2012-04-11 66 views
3

我必须遍历整数的二维数组中的所有项目并更改值(根据某些规则,不重要)。Python:是多维数组超级缓慢的迭代吗?

我很惊讶python运行时和C#或java运行时间之间在性能上有什么显着差异。我写了完全错误的Python代码(v2.7.2)吗?

import numpy 
a = numpy.ndarray((5000,5000), dtype = numpy.int32) 
for x in numpy.nditer(a.T): 
    x = 123 
>python -m timeit -n 2 -r 2 -s "import numpy; a = numpy.ndarray((5000,5000), dtype=numpy.int32)" "for x in numpy.nditer(a.T):" " x = 123" 
2 loops, best of 2: 4.34 sec per loop 

例如C#代码只执行50毫秒,即蟒较慢几乎100倍! (假设matrix变量已经初始化)

for (y = 0; y < 5000; y++) 
for (x = 0; x < 5000; x++) 
    matrix[y][x] = 123; 
+1

为什么你会惊讶于解释语言比JIT编译语言慢?你有没有试过用[PyPy](http://pypy.org/)代替CPython? – 2012-04-11 19:41:10

+9

使用Numpy是为了避免显式的Python循环,并使用向量化的NumPy函数。 – 2012-04-11 19:42:04

+1

@AdamRosenfield:PyPy还没有NumPy支持。 – 2012-04-11 19:43:01

回答

4

Python是比C或C#更加动态的语言。循环太慢的主要原因在于,在每次传递时,CPython解释器都在做一些浪费时间的额外工作:具体来说,它将名称x与迭代器中的下一个对象绑定,然后当它评估赋值时必须再次查找名称x

正如@Sven Marnach指出的那样,您可以调用方法函数numpy.fill()并且速度很快。该函数编译为C或Fortran,它将简单地遍历numpy.array数据结构的地址并填充值。比Python更加动态,这对于这种简单的情况非常有用。

但现在考虑PyPy。一旦你在PyPy下运行你的程序,一个JIT分析你的代码实际上在做什么。在这个例子中,它指出x这个名字除了赋值之外没有其他用途,它可以优化去绑定这个名字。这个例子应该是PyPy极速加速的例子; PyPy可能比普通Python快10倍(所以只有C的十分之一快,而不是1/100)。

http://pypy.org

据我所知,PyPy将不会被numpy的工作了一段时间呢,所以你不能只是运行PyPy您现有的NumPy的代码呢。但是这一天即将到来。

我对PyPy很兴奋。它提供了希望,我们可以用一种非常高级的语言(Python)编写,但几乎可以获得用“便携式汇编语言”(C)编写东西的性能。对于这样的例子来说,Numpy甚至可以通过使用来自CPU的SSD指令(SSE2,NEON或其他)来优化幼稚C代码的性能。对于这个例子,使用SIMD,可以将每个循环的四个整数设置为123,这比普通的C循环快。 (除非C编译器也使用了SIMD优化!可以想象,这种情况很可能出现这种情况,因此我们可以回到“接近C的速度”而不是更快的速度,但我们可以想象出更复杂的情况C编译器不够智能,无法优化,未来的PyPy可能会这么做)。

但是现在不要介意PyPy。如果您将与Numpy合作,最好学习像numpy.fill()这样的功能来加速您的代码。

13

是的!在Python中通过numpy数组迭代很慢。 (比迭代python列表慢)

通常,您避免直接迭代它们。

如果你可以给我们一个基于你改变事物的规则的例子,那么很容易矢量化。

作为玩具例如:

import numpy as np 

x = np.linspace(0, 8*np.pi, 100) 
y = np.cos(x) 

x[y > 0] = 100 

然而,在必须进行迭代,或者由于算法(例如有限差分法)或以减少临时阵列的存储器成本的许多情况。

在这种情况下,看看CythonWeave或类似的东西。

+0

请在这些帖子中提及cpython。它实际上在pypy上更快或不更慢。 – fijal 2012-04-14 17:28:22

+0

我提到了cpython(或者我暗示cpython与numpy解决方案)。除非你提出一个纯粹的Python解决方案? (例如'从数学导入cos; y = [cos(项目)for x]')我很困惑... – 2012-04-15 01:18:43

+0

好pypy有numpy支持(在某种程度上),所以隐含的部分是无效的。 – fijal 2012-04-16 15:38:06

10

你给了大概只是设定一个二维数组NumPy的123到所有项目的例子这可以有效地完成这样的:

a.fill(123) 

a[:] = 123 
+5

对于示例5000x5000阵列,'numpy.fill'需要50ms,其中等效循环需要3秒才能在机器上输入...... – talonmies 2012-04-11 19:55:36

+0

@talonmies在我的机器上甚至30ms;) – 2012-04-11 20:58:40

3

C++强调机器时间超过程序员时间。

Python强调程序员时间超过机器时间。

Pypy是一个用python编写的python,它们有numpy的开头;你可以试试。 Pypy有一个很好的JIT,让事情变得相当快。

你也可以尝试使用cython,它可以将Python的方言转换为C,并将C编译为Python C扩展模块;这使得人们可以继续使用CPython来获取大部分代码,同时仍然可以获得一些加速。然而,在我试过比较Pypy和Cython的微基准测试中,Pypy比Cython快得多。

Cython使用高度pythonish语法,但它允许您非常自由地混合Python数据类型与C数据类型。如果你用C数据类型重做你的热点,它应该是非常快的。 Cython也继续使用Python数据类型,但并没有那么多。

0

nditer代码没有为a的元素赋值。这并不影响计时问题,但我提到它,因为它不应被视为nditer的良好用法。

正确版本是:

for i in np.nditer(a, op_flags=[["readwrite"]]): 
    i[...] = 123 

[...]是需要保留提及环价值,这是形状()的阵列。

使用A.T没有意义,因为它的基数A的值发生了变化。

我同意做这个任务的正确方法是a[:]=123