這是正常的預期行爲。這太簡單了,不適用「避免與numpy循環」聲明everywere。如果你正在處理內部循環,它幾乎總是如此。但是在外部循環的情況下(比如你的情況)有更多的例外。特別是如果替代方案是使用廣播,因爲這通過使用很多內存加速您的操作。
只是爲了一點背景知識添加到「避免與numpy的循環」聲明:
NumPy的數組存儲爲連續陣列,c類型。 Python int
與C int
不一樣!因此,無論何時迭代數組中的每個項目,都需要從數組中插入項目,將其轉換爲Python int
,然後執行任何您想要的操作,最後您可能需要再次將其轉換爲ac整數(稱爲裝箱和拆箱的價值)。例如,你想用Python sum
在數組中的項目:
import numpy as np
arr = np.arange(1000)
%%timeit
acc = 0
for item in arr:
acc += item
# 1000 loops, best of 3: 478 µs per loop
您更好地使用numpy的:
%timeit np.sum(arr)
# 10000 loops, best of 3: 24.2 µs per loop
即使你將循環推Python的C代碼你遠離numpy表現:
%timeit sum(arr)
# 1000 loops, best of 3: 387 µs per loop
從這條規則可能會有例外,但這些將是非常稀疏。至少只要有一些等效的numpy功能。所以如果你想遍歷單個元素,那麼你應該使用numpy。
有時一個普通的python循環就足夠了。這不是廣告宣傳,但與Python功能相比,numpy功能有很大的開銷。例如考慮一個3元素陣列:
arr = np.arange(3)
%timeit np.sum(arr)
%timeit sum(arr)
哪一個會更快?
解決方案:Python的功能性能比numpy的更好的解決方案:
# 10000 loops, best of 3: 21.9 µs per loop <- numpy
# 100000 loops, best of 3: 6.27 µs per loop <- python
但是這是什麼都與你的榜樣呢?事實上並非如此,因爲您總是在陣列上使用numpy函數(而不是單個元素,甚至幾個元素),所以內部循環已經使用了優化函數。這就是爲什麼兩者執行大致相同(+/-約10倍,只有很少的元素在約500個元素的因子2)。但這不是真正的循環開銷,而是函數調用開銷!
你的循環液
使用line-profiler和resolution = 100
:
def fun_func(tim, prec, values):
for i, ti in enumerate(tim):
values[i] = np.sum(np.sin(prec * ti))
%lprun -f fun_func fun_func(tim, prec, values)
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def fun_func(tim, prec, values):
2 101 752 7.4 5.7 for i, ti in enumerate(tim):
3 100 12449 124.5 94.3 values[i] = np.sum(np.sin(prec * ti))
95%的循環中度過的,我甚至循環體分裂成幾個部分來驗證這一點:
def fun_func(tim, prec, values):
for i, ti in enumerate(tim):
x = prec * ti
x = np.sin(x)
x = np.sum(x)
values[i] = x
%lprun -f fun_func fun_func(tim, prec, values)
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def fun_func(tim, prec, values):
2 101 609 6.0 3.5 for i, ti in enumerate(tim):
3 100 4521 45.2 26.3 x = prec * ti
4 100 4646 46.5 27.0 x = np.sin(x)
5 100 6731 67.3 39.1 x = np.sum(x)
6 100 714 7.1 4.1 values[i] = x
消費者的時間是np.multiply
,np.sin
,np.sum
在這裏,你可以輕鬆地CK通過每次呼叫與他們的開銷比較它們的時間:
arr = np.ones(1, float)
%timeit np.sum(arr)
# 10000 loops, best of 3: 22.6 µs per loop
所以只要比你有類似的運行時間計算運行時COMULATIVE函數調用的開銷很小。即使有100個項目,您也非常接近間接費用時間。訣竅是知道他們在哪個時間點保本。隨着1000項調用的開銷仍然顯著:
%lprun -f fun_func fun_func(tim, prec, values)
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def fun_func(tim, prec, values):
2 1001 5864 5.9 2.4 for i, ti in enumerate(tim):
3 1000 42817 42.8 17.2 x = prec * ti
4 1000 119327 119.3 48.0 x = np.sin(x)
5 1000 73313 73.3 29.5 x = np.sum(x)
6 1000 7287 7.3 2.9 values[i] = x
但隨着resolution = 5000
相比,運行時的開銷是相當低:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def fun_func(tim, prec, values):
2 5001 29412 5.9 0.9 for i, ti in enumerate(tim):
3 5000 388827 77.8 11.6 x = prec * ti
4 5000 2442460 488.5 73.2 x = np.sin(x)
5 5000 441337 88.3 13.2 x = np.sum(x)
6 5000 36187 7.2 1.1 values[i] = x
當你在每個np.sin
花500US叫你不要關心20us的開銷了。
一句話可能是:line_profiler
可能包含一些額外的每行開銷,也可能是每個函數調用,所以函數調用開銷忽略的點可能會更低!!!
你的廣播解決方案
我開始剖析的第一個解決方案,讓我們做同樣的與第二個解決方案:
def fun_func(tim, prec, values):
x = tim[:, np.newaxis]
x = x * prec
x = np.sin(x)
x = np.sum(x, axis=1)
return x
再次使用line_profiler與resolution=100
:
%lprun -f fun_func fun_func(tim, prec, values)
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def fun_func(tim, prec, values):
2 1 27 27.0 0.5 x = tim[:, np.newaxis]
3 1 638 638.0 12.9 x = x * prec
4 1 3963 3963.0 79.9 x = np.sin(x)
5 1 326 326.0 6.6 x = np.sum(x, axis=1)
6 1 4 4.0 0.1 return x
這已經大大超過了開銷時間,因此與循環相比,我們的結果快了10倍。
我也做了分析爲resolution=1000
:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def fun_func(tim, prec, values):
2 1 28 28.0 0.0 x = tim[:, np.newaxis]
3 1 17716 17716.0 14.6 x = x * prec
4 1 91174 91174.0 75.3 x = np.sin(x)
5 1 12140 12140.0 10.0 x = np.sum(x, axis=1)
6 1 10 10.0 0.0 return x
與precision=5000
:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def fun_func(tim, prec, values):
2 1 34 34.0 0.0 x = tim[:, np.newaxis]
3 1 333685 333685.0 11.1 x = x * prec
4 1 2391812 2391812.0 79.6 x = np.sin(x)
5 1 280832 280832.0 9.3 x = np.sum(x, axis=1)
6 1 14 14.0 0.0 return x
的1000尺寸仍然較快,但正如我們所看到那裏調用的開銷仍不 - 在環路解決方案中不可用。但對於resolution = 5000
中的每個步驟所花費的時間幾乎是相同的(有些是有點慢,別人快,但整體頗爲相似)
的另一個影響是,實際廣播當你做乘法變得顯著。即使有非常聰明的numpy解決方案,它仍然包含一些額外的計算。對於resolution=10000
你看到廣播乘法開始佔用更多的「%的時間」相對於循環解決方案:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def broadcast_solution(tim, prec, values):
2 1 37 37.0 0.0 x = tim[:, np.newaxis]
3 1 1783345 1783345.0 13.9 x = x * prec
4 1 9879333 9879333.0 77.1 x = np.sin(x)
5 1 1153789 1153789.0 9.0 x = np.sum(x, axis=1)
6 1 11 11.0 0.0 return x
Line # Hits Time Per Hit % Time Line Contents
==============================================================
8 def loop_solution(tim, prec, values):
9 10001 62502 6.2 0.5 for i, ti in enumerate(tim):
10 10000 1287698 128.8 10.5 x = prec * ti
11 10000 9758633 975.9 79.7 x = np.sin(x)
12 10000 1058995 105.9 8.6 x = np.sum(x)
13 10000 75760 7.6 0.6 values[i] = x
但有實際的,除了時間還有一件事花:內存消耗。你的循環解決方案需要O(n)
內存,因爲你總是處理n
元素。但是,廣播解決方案需要O(n*n)
內存。如果在循環中使用resolution=20000
,您可能必須等待一段時間,但它仍然只需要8bytes/element * 20000 element ~= 160kB
,但在廣播中需要~3GB
。這忽略了常數因素(如臨時數組又稱中間數組)!假設你走得更遠,你會非常快地耗盡內存!
時間再總結幾點:
- 如果你在你這樣做是錯誤的一個numpy的陣列做在單品蟒蛇循環。
- 如果循環遍歷numpy數組的子陣列,請確保每個循環中的函數調用開銷與函數耗用的時間相比可忽略不計。
- 如果您廣播numpy陣列,請確保您沒有耗盡內存。
但是關於優化最重要的一點仍然是:
最終的一個想法:
,要麼需要一個迴路或廣播這樣的功能可使用cython,numba或numexpr容易地實現,如果沒有已經在numpy或scipy現有的解決方案。
例如,結合了來自在低resolutions
廣播解決方案的速度循環溶液中的存儲器效率是這樣的一個numba功能:
from numba import njit
import math
@njit
def numba_solution(tim, prec, values):
size = tim.size
for i in range(size):
ti = tim[i]
x = 0
for j in range(size):
x += math.sin(prec[j] * ti)
values[i] = x
作爲評價numexpr
還可以評估指出廣播的計算速度非常快,,而不需要內存O(n*n)
:
>>> import numexpr
>>> tim_2d = tim[:, np.newaxis]
>>> numexpr.evaluate('sum(sin(tim_2d * prec), axis=1)')
真的,我建立的方波,但不污染問題的係數,我簡化了例子。 – godaygo
你有多少內存?如果它不夠大,'tim [:, np.newaxis] * prec'可能需要交換空間,這會導致性能下降。 – unutbu
你如何對兩個功能進行基準測試? –