傳遞 a numpy.ndarrayof uint8tonumpy.logical_and時,如果我應用numpy.view(bool)它的輸入,它的運行速度會顯著加快。
a = np.random.randint(0, 255, 1000 * 1000 * 100, dtype=np.uint8)
b = np.random.randint(0, 255, 1000 * 1000 * 100, dtype=np.uint8)
%timeit np.logical_and(a, b)
126 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit np.logical_and(a.view(bool), b.view(bool))
20.9 ms ± 110 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)
有人可以解釋為什么會這樣嗎?
此外,為什么numpy.logical_and不自動應用于view(bool)陣列uint8?(有什么情況我們不應該使用view(bool)?)
編輯:
看來這是Windows環境的問題。我只是在官方 python docker 容器(debian)中嘗試了同樣的事情,發現它們之間沒有區別。
我的環境:
- 作業系統:Windows 10 專業版 21H2
- CPU:AMD銳龍9 5900X
- Python:Win32 上的 Python 3.10.2(tags/v3.10.2:a58ebcc,2022 年 1 月 17 日,14:12:15)[MSC v.1929 64 位(AMD64)]
- 麻木:1.22.2
uj5u.com熱心網友回復:
這是當前 Numpy 實作的性能問題。我也可以在 Windows 上重現這個問題(使用帶有 Numpy 1.20.3 的 Intel Skylake Xeon 處理器)。基于慢速條件跳轉np.logical_and(a, b)執行效率非常低的標量匯編代碼,同時執行相對快速的SIMD 指令。np.logical_and(a.view(bool), b.view(bool))
目前,Numpy對-types使用特定的實作。bool關于所使用的編譯器,如果用于構建 Numpy 的編譯器未能自動對代碼進行矢量化,這在 Windows 上顯然是這種情況(并解釋為什么在其他平臺上不是這種情況,因為編譯器是可能不完全相同)。Numpy 代碼可以針對非bool型別進行改進。請注意,Numpy 的矢量化是一項正在進行的作業,我們計劃很快對其進行優化。
更深入的分析
這是由執行的匯編代碼np.logical_and(a, b):
Block 24:
cmp byte ptr [r8], 0x0 ; Read a[i]
jz <Block 27> ; Jump to block 27 if a[i]!=0
Block 25:
cmp byte ptr [r9], 0x0 ; Read b[i]
jz <Block 27> ; Jump to block 27 if b[i]!=0
Block 26:
mov al, 0x1 ; al = 1
jmp <Block 28> ; Skip the next instruction
Block 27:
xor al, al ; al = 0
Block 28:
mov byte ptr [rdx], al ; result[i] = al
inc r8 ; i = 1
inc rdx
inc r9
sub rcx, 0x1
jnz <Block 24> ; Loop again while i<a.shape[0]
如您所見,回圈使用幾個資料相關的條件跳轉來寫入a和b讀取每個專案。這是非常低效的,因為處理器無法用隨機值預測所采用的分支。結果,處理器停頓了幾個周期(在現代 x86 處理器上通常大約 10 個周期)。
這是由執行的匯編代碼np.logical_and(a.view(bool), b.view(bool)):
Block 15:
movdqu xmm1, xmmword ptr [r10] ; xmm1 = a[i:i 16]
movdqu xmm0, xmmword ptr [rbx r10*1] ; xmm0 = b[i:i 16]
lea r10, ptr [r10 0x10] ; i = 16
pcmpeqb xmm1, xmm2 ; \
pandn xmm1, xmm0 ; | Complex sequence to just do:
pcmpeqb xmm1, xmm2 ; | xmm1 &= xmm0
pandn xmm1, xmm3 ; /
movdqu xmmword ptr [r14 r10*1-0x10], xmm1 ; result[i:i 16] = xmm1
sub rcx, 0x1
jnz <Block 15> ; Loop again while i!=a.shape[0]//16
此代碼使用稱為 SSE 的SIMD 指令集,它能夠在 128 位寬的暫存器上作業。沒有條件跳轉。這段代碼效率更高,因為它每次迭代一次對 16 個專案進行操作,并且每次迭代應該更快。
請注意,最后一個代碼也不是最優的,因為大多數現代 x86 處理器(如您的 AMD 處理器)都支持 256 位 AVX-2 指令集(速度是其兩倍)。此外,編譯器會生成一個效率低下的 SIMD 指令序列來執行邏輯,并且可以對其進行優化。編譯器似乎假設布林值可以是 0 或 1 不同的值。話雖如此,輸入陣列太大而無法放入 CPU 快取中,因此代碼受 RAM 吞吐量的限制,而不是第一個. 這就是為什么 SIMD 友好的代碼沒有明顯更快的原因。如果您的處理器上的陣列小于 1 MiB(就像幾乎所有其他現代處理器一樣),這兩個版本之間的差異肯定要大得多。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/432696.html
上一篇:為什么將const屬性應用于純函式不能減少經過的時間?
下一篇:如何在不使用的情況下修改集合?
