2012-04-11 65 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