列表理解,映射和numpy.vectorize性能

mcs*_*her 11 python performance numpy list-comprehension

我有一个函数foo(i),它接受一个整数并花费大量的时间来执行.会不会有任何的初始化通过以下方式之间的显著性能差异:

a = [foo(i) for i in xrange(100)]

a = map(foo, range(100))

vfoo = numpy.vectorize(foo)
a = vfoo(range(100))
Run Code Online (Sandbox Code Playgroud)

(我不关心输出是列表还是numpy数组.)

有没有更好的办法?

Mik*_*ham 18

  • 你为什么要优化这个?您是否编写过工作,测试过的代码,然后检查您的算法是否描述了您的代码并发现优化它会产生影响?你是在一个深层内循环中做到这一点,你发现你正在花时间吗?如果没有,请不要打扰.

  • 通过计时,你只会知道哪种方法最快.要以有用的方式计时,您必须专门针对您的实际用例.例如,您可以在列表推导中的函数调用与内联表达式之间获得明显的性能差异; 目前尚不清楚你是否真的想要前者,或者你是否将其减少到那个以使你的案件相似.

  • 你说无论你最终是一个numpy数组还是a list,都没关系,但如果你正在进行这种微优化,它确实很重要,因为当你以后使用它们时它们的表现会有所不同.把手指放在那里可能会很棘手,所以希望结果是整个问题都是不成熟的.

  • 为了清晰,可读性等,通常最好简单地使用正确的工具.我很难在这些事情之间做出决定.

    • 如果我需要numpy数组,我会使用它们.我会用它们来存储大型同构数组或多维数据.我经常使用它们,但很少我认为我想使用列表.
      • 如果我使用这些,我会尽力编写已经矢量化的函数,所以我没有必要使用numpy.vectorize.例如,times_five下面可以在没有装饰的numpy数组上使用.
    • 如果我没有理由使用numpy,也就是说如果我没有解决数值数学问题或使用特殊的numpy功能或存储多维数组或其他什么......
      • 如果我有一个已经存在的功能,我会用map.这就是它的用途.
      • 如果我有一个适合小表达式的操作而且我不需要函数,我会使用列表理解.
      • 如果我只想对所有情况进行操作但实际上不需要存储结果,我会使用plain for循环.
      • 在许多情况下,我实际上使用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
Run Code Online (Sandbox Code Playgroud)

一个天真的观察者会得出结论,地图是这些选项中表现最好的,但答案仍然是"它取决于".考虑使用您正在使用的工具的好处的能力:列表推导让您避免定义简单的功能; 如果你正在做正确的事情,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
Run Code Online (Sandbox Code Playgroud)

但那不是全部 - 还有更多.考虑算法变化的力量.它可能更具戏剧性.

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
Run Code Online (Sandbox Code Playgroud)

有时,算法更改可能更有效.随着数字越来越大,这将越来越有效.

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
Run Code Online (Sandbox Code Playgroud)

即使是现在,这一切都可能对您的实际问题影响不大.如果你能正确使用numpy看起来很棒,但它有其局限性:这些numpy示例都没有使用数组中的实际Python对象.这使必须做的事情复杂化; 很多甚至.如果我们确实使用C数据类型怎么办?这些不如Python对象强大.它们不可空.整数溢出.你必须做一些额外的工作来检索它们.他们是静态打字的.有时这些事情被证明是问题,甚至是意外问题.

所以,你去了:一个确定的答案."这取决于."


# 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
Run Code Online (Sandbox Code Playgroud)


wes*_*cpy 9

我的第一个评论是你应该使用xrange()或range()在你的所有例子中.如果你混合它们,那么你要比较苹果和橘子.

我的第二个@ Gabe的观点是,如果你有很多数据结构而且它们很大,那么numpy应该会赢得整体...只要记住大部分时间C比Python快,但是大多数时候, PyPy比CPython更快. :-)

就listcomps与map()call调用而言...一个调用101个函数调用而另一个函数调用102.你不会看到时间上的显着差异,如下所示使用timeit模块,如@Mark建议:

  • 列表理解

    $ python -m timeit "def foo(x):pass; [foo(i) for i in range(100)]"
    1000000 loops, best of 3: 0.216 usec per loop
    $ python -m timeit "def foo(x):pass; [foo(i) for i in range(100)]"
    1000000 loops, best of 3: 0.21 usec per loop
    $ python -m timeit "def foo(x):pass; [foo(i) for i in range(100)]"
    1000000 loops, best of 3: 0.212 usec per loop

  • map() 功能调用

    $ python -m timeit "def foo(x):pass; map(foo, range(100))"
    1000000 loops, best of 3: 0.216 usec per loop
    $ python -m timeit "def foo(x):pass; map(foo, range(100))"
    1000000 loops, best of 3: 0.214 usec per loop
    $ python -m timeit "def foo(x):pass; map(foo, range(100))"
    1000000 loops, best of 3: 0.215 usec per loop

尽管如此,我还会这样说:除非你打算使用从这些技术中的任何一种创建的列表,否则我会完全避免它们.换句话说,如果你要做的就是迭代这些数据,那么当你只需要一次查看每个结果并尽快丢弃列表时,在内存中创建一个潜在的大量列表是不值得的额外内存消耗因为你已经完成了它.

在这种情况下,我强烈建议使用生成器表达式.genexps不会在内存中创建整个列表...它是一种更加内存友好,懒惰的循环元素循环迭代方式.最好的部分是它的语法几乎与listcomps的语法相同:

a = (foo(i) for i in range(100))
Run Code Online (Sandbox Code Playgroud)

沿着更多迭代的路线,更改所有对2.x版本的剩余部分的range()调用,xrange()然后将它们切换回range()移植到Python 3作为xrange()替换并重命名为range().:-)


Gab*_*abe 7

如果函数本身需要花费大量时间来执行,那么将其输出映射到数组是无关紧要的.一旦你开始进入数百万个数字的数组,numpy可以为你节省大量的内存.

  • 请注意,使用`numpy.vectorize`实际上并没有像使用真正的numpy操作那样有效地将事物移动到C中. (2认同)