我有一个函数foo(i),它需要一个整数参数,并且执行起来需要相当长的时间。在下列初始化 a 的方式中,是否会有显著的性能差异:
a = [foo(i) for i in xrange(100)]
a = map(foo, range(100))
vfoo = numpy.vectorize(foo)
a = vfoo(range(100))
(我不在意输出结果是列表还是NumPy数组。)
有更好的方法吗?
我有一个函数foo(i),它需要一个整数参数,并且执行起来需要相当长的时间。在下列初始化 a 的方式中,是否会有显著的性能差异:
a = [foo(i) for i in xrange(100)]
a = map(foo, range(100))
vfoo = numpy.vectorize(foo)
a = vfoo(range(100))
(我不在意输出结果是列表还是NumPy数组。)
有更好的方法吗?
为什么要对这个进行优化?你是否编写了可工作并经过测试的代码,然后分析了你的算法的性能并发现优化将产生影响?你是否在深层循环中执行此操作,且发现你正在花费时间?如果不是,请不要麻烦。
只有通过计时,您才会知道哪个方法最快适合您。为了以有用的方式计时它,您必须将其专门用于实际用例。例如,在列表推导式中调用函数与内联表达式之间可能存在明显的性能差异;您不确定您是否真正需要前者或者您是否将其简化为使您的情况相似。
您说无论您最终获得numpy数组还是list
都没有关系,但如果您进行这种微小优化,那就会关系,因为在随后使用它们时,它们的性能会有所不同。找出问题可能有些棘手,因此希望整个问题都是过早的。
通常最好只是使用正确的工具来提高清晰度、可读性等方面。很少有情况使我难以决定使用这些工具之间的哪一个。
numpy.vectorize
。例如,下面的times_five
可以在numpy数组上使用,而不需要修饰。map
。那是它的作用。map
和列表推导式的惰性版本:itertools.imap
和生成器表达式。在某些情况下,它们可以将内存使用减少n
倍,并且有时可以避免执行不必要的操作。如果确实是性能问题所在,解决这类问题就很棘手。非常普遍的情况是人们为他们的实际问题计时错误的玩具案例。更糟糕的是,人们基于此制定愚蠢的一般规则也极其普遍。
请考虑以下情况(timeme.py在下面发布)
python -m timeit "from timeme import x, times_five; from numpy import vectorize" "vectorize(times_five)(x)"
1000 loops, best of 3: 924 usec per loop
python -m timeit "from timeme import x, times_five" "[times_five(item) for item in x]"
1000 loops, best of 3: 510 usec per loop
python -m timeit "from timeme import x, times_five" "map(times_five, x)"
1000 loops, best of 3: 484 usec per loop
一个天真的观察者可能会得出结论,map是这些选项中表现最佳的,但答案仍然是“取决于情况”。考虑使用你正在使用的工具的好处:列表生成式让你避免定义简单的函数;如果你正在做正确的事情,numpy让你向量化C。
python -m timeit "from timeme import x, times_five" "[item + item + item + item + item for item in x]"
1000 loops, best of 3: 285 usec per loop
python -m timeit "import numpy; x = numpy.arange(1000)" "x + x + x + x + x"
10000 loops, best of 3: 39.5 usec per loop
但这还不是全部,还有更多。考虑一下算法改变的威力,它可能会更加显著。
python -m timeit "from timeme import x, times_five" "[5 * item for item in x]"
10000 loops, best of 3: 147 usec per loop
python -m timeit "import numpy; x = numpy.arange(1000)" "5 * x"
100000 loops, best of 3: 16.6 usec per loop
有时算法的改变可能会更加有效。随着数字的增加,这种方法会越来越有效。python -m timeit "from timeme import square, x" "map(square, x)"
10 loops, best of 3: 41.8 msec per loop
python -m timeit "from timeme import good_square, x" "map(good_square, x)"
1000 loops, best of 3: 370 usec per loop
即使现在,所有这些可能与你实际的问题没有太大关系。它看起来像是numpy非常好用,如果你能使用它,但它有其局限性:这些numpy示例中没有使用数组中的实际Python对象。这使得必须完成的工作变得更加复杂,甚至是很多。如果我们确实要使用C数据类型怎么办?这些类型不如Python对象健壮,它们不能为null值,整数会溢出,你需要做一些额外的工作才能检索到它们,它们是静态类型的。有时候这些东西会证明成问题,甚至是意想不到的问题。
所以,这就是一个明确的答案:"这取决于情况。"
# timeme.py
x = xrange(1000)
def times_five(a):
return a + a + a + a + a
def square(a):
if a == 0:
return 0
value = a
for i in xrange(a - 1):
value += a
return value
def good_square(a):
return a ** 2
首先评论:不要在您的示例中混合使用xrange()
或range()
...这样做会使您的问题无效,因为您正在比较苹果和橙子。
我赞同@Gabe的观点,如果您有许多大型数据结构,则numpy应该总体上胜出...只需记住大多数情况下C比Python快,但是再次提醒,大多数情况下PyPy比CPython更快。:-)
至于列表推导式与map()
调用的比较...一个需要进行101个函数调用,而另一个需要进行102个调用。这意味着您不会看到时间上的显着差异,如@Mike建议所示,使用timeit模块:
列表推导式
$ python -m timeit "def foo(x):pass; [foo(i) for i in range(100)]"
3次循环中,最佳用时:0.216 微秒/每次循环
$ python -m timeit "def foo(x):pass; [foo(i) for i in range(100)]"
3次循环中,最佳用时:0.21 微秒/每次循环
$ python -m timeit "def foo(x):pass; [foo(i) for i in range(100)]"
3次循环中,最佳用时:0.212 微秒/每次循环
map()
函数调用
$ python -m timeit "def foo(x):pass; map(foo, range(100))"
3次循环中,最佳用时:0.216 微秒/每次循环
$ python -m timeit "def foo(x):pass; map(foo, range(100))"
3次循环中,最佳用时:0.214 微秒/每次循环
$ python -m timeit "def foo(x):pass; map(foo, range(100))"
3次循环中,最佳用时:0.215 微秒/每次循环
话虽如此,除非您计划从这两种技术创建的列表中使用它们,否则请尽量避免使用它们(使用列表)。也就是说,如果您只是在它们上面进行迭代,那么当您仅关心逐个查看每个元素时,它不值得消耗内存(可能会创建一个潜在的大型列表)。完成后立即丢弃列表。
在这种情况下,我强烈建议使用生成器表达式,因为它们不会在内存中创建整个列表... 这是一种更加内存友好、惰性迭代的方式,用于循环处理元素而不创建一个较大的数组在内存中。最好的部分是它的语法几乎与listcomps相同:
a = (foo(i) for i in range(100))
仅适用于2.x用户:沿着更多迭代的方向,将所有 range()调用更改为
xrange()以适应任何旧的2.x代码,然后在移植到Python 3时切换到
range(),其中
xrange()被替换并重命名为
range()。
f
。在我的机器上,python -m timeit "def foo(x):pass; [None for i in range(100)]"
给出的结果比你的列表推导使用快大约2/3的时间。这是否是OP想要的?我不知道,但这确实表明这些问题是微妙的,结论往往更多地反映了我们如何设计我们的示例,而不是任何真正用途的东西。 - Mike Grahammap()
也不需要函数,例如map(None, range(100))
。我有一个更长的关于性能和列表推导式与map()
的演讲,但是OP没有问这个问题,所以我不能在这里回答。我可以说的是,为了真正加速列表推导式,你必须将那个函数简化为一个表达式并使用它(而不是函数)。函数调用会有性能惩罚,在紧密循环中放大。 - wescpy如果函数本身执行需要很长时间,那么将其输出映射到数组中是无关紧要的。然而,一旦你开始处理数百万个数字的数组,numpy可以为你节省大量的内存。
numpy.vectorize
实际上并不能像真正的 numpy 操作一样有效地将事物移动到 C 中。 - Mike Graham没有psyco:
list comprehension: 47.5581952455 ms
map: 51.9082732582 ms
numpy.vectorize: 57.9601876775 ms
使用Psyco:
list comprehension: 30.4318844993 ms
map: 96.4504427239 ms
numpy.vectorize: 99.5858691538 ms