簡體   English   中英

list(numpy.array)變慢的原因是什么?

[英]What is the reason for slowness of list(numpy.array)?

眾所周知,如果a是一個numpy數組,則a.tolist()list(a)更快,例如:

>>> import numpy as np
>>> big_np=np.random.randint(1,10**7,(10**7,))

>>> %timeit list(big_np)
1 loop, best of 3: 869 ms per loop
>>> %timeit big_np.tolist()
1 loop, best of 3: 306 ms per loop

這意味着,朴素的list(a)版本比特殊功能tolist()慢約3 tolist()

但是,將其與內置array -module的性能進行比較:

>>> import array
>>> big_arr=array.array('i', big_np)
>>> %timeit list(big_arr)
1 loop, best of 3: 312 ms per loop

我們可以看到,也許應該說list(a)慢而不是tolist()快,因為array.array和特殊函數一樣快。

另一個觀察結果: array.array -module和tolist受益於small-integer-pool (即,當值在[-5, 256] tolist [-5, 256]范圍內時),但是list(a)並非如此:

##only small integers:
>>> small_np=np.random.randint(1,250, (10**7,))
>>> small_arr=array.array('i', small_np)

>>> %timeit list(small_np)
1 loop, best of 3: 873 ms per loop
>>> %timeit small_np.tolist()
10 loops, best of 3: 188 ms per loop
>>> %timeit list(small_arr)
10 loops, best of 3: 150 ms per loop

我們可以看到,較快的版本比以前快2倍左右,但較慢的版本和以前一樣慢。

我的問題:與list(numpy.array)相比, list(array.array)速度降低了多少?

編輯:

再觀察一下,對於Python2.7,如果整數更大(即不能由int32保持),則需要更長的時間:

>>> very_big=np.random.randint(1,10**7,(10**7,))+10**17
>>> not_so_big=np.random.randint(1,10**7,(10**7,))+10**9
>>> %timeit very_big.tolist()
1 loop, best of 3: 627 ms per loop
>>> %timeit not_so_big.tolist()
1 loop, best of 3: 302 ms per loop

但仍比緩慢的列表版本更快。

這是部分答案,解釋您對小型整數池的觀察:

>>> a = np.arange(10)
>>> type(list(a)[0])
<class 'numpy.int64'>
>>> type(a.tolist()[0])
<class 'int'>

正如我們所看到的, tolist似乎嘗試創建本機python類型的元素,而數組迭代器(列表構造函數使用的數組)不會打擾。

確實, tolist的C實現( 在此處提供源代碼)使用PyArray_GETITEM ,它等效於Python arr[index].item() ,而不是-可能有人假設arr[index].item() arr[index]

基本上,Paul Panzer的答案解釋了會發生的情況:在慢list(...)版本中,列表的結果元素不是python-integers,而是numpy- numpy.int64 ,例如numpy.int64 這個答案只說明一點點,並連接點。

我沒有進行系統的分析,但是當在調試器中停止運行時,每次兩個版本都在創建整數對象的例程中,因此很可能這是執行時間的主要部分,並且開銷並不重要。

list(..) version迭代器調用array_item ,它對一維數組有特殊處理,並調用PyArray_Scalar ,這是一個非常通用的函數,並且不使用Pythons-integer-creation的機制。 它恰好比Python版本慢,對於小值也沒有整數池。

.tolist()版本調用recursive_tolist ,該版本最終使用Python的PyLong_FromLong(long) ,它顯示了所有觀察到的行為,並且碰巧比numpy功能更快(可能是因為這不是使用numpy的正常方式,沒有很多優化措施)完成)。

與Python3相比,Python2的差別很小:Python2具有兩種不同的整數類別:一類效率更高,適用於32位以下的數字,另一類適用於任意大數-因此,對於較大的數字,最通用的(因此更多昂貴)的路徑必須走-這也是可以觀察到的。

使用list(something)構造一個列表會迭代某些內容,並將迭代結果收集到一個新列表中。

如果list(small_np)list(small_arr)慢,則可以假定對small_np迭代要比對small_arr迭代慢。 讓我們驗證一下:

%timeit for i in small_np: pass   # 1 loop, best of 3: 916 ms per loop
%timeit for i in small_arr: pass  # 1 loop, best of 3: 261 ms per loop

是的,迭代一個numpy數組似乎比較慢。 這是我必須開始推測的地方。 numpy數組很靈活。 它們可以具有任意數量的尺寸,並具有不同的跨度。 數組數組總是平坦的。 這種靈活性可能需要付出一定的代價,這體現在更復雜的迭代中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM