我正在嘗試在 Cython 中回圈 2 個二維陣列。陣列具有以下形狀:
ranges_1是 6000x3 的陣列int64,而ranges_22000x2 是int64。此迭代需要執行大約 10000 次。這意味著嵌套 for 回圈內的計算總數約為 2000x6000x10000 = 1200 億次。
這是我用來生成“虛擬”資料的代碼:
import numpy as np
ranges_1 = np.stack([np.random.randint(0, 10_000, 6_000), np.random.randint(0, 10_000, 6_000), np.arange(0, 6_000)], axis=1)
ranges_2 = np.stack([np.random.randint(0, 10_000, 2_000), np.random.randint(0, 10_000, 2_000)], axis=1)
這給出了 2 個這樣的陣列:
array([[6131, 1478, 0],
[9317, 7263, 1],
[7938, 6249, 2],
...,
[5153, 426, 5997],
[9164, 9211, 5998],
[1695, 1792, 5999]])
和:
array([[ 433, 558],
[3420, 2494],
[6367, 7916],
...,
[8693, 1692],
[1256, 9013],
[4096, 1860]])
我嘗試的第一個實作是一個“樸素”的版本,它如下(里面的函式只是一個使用陣列中所有資料的測驗函式):
import numpy as np
cimport numpy as np
cimport cython
ctypedef np.int_t DTYPE_t
def test_func(np.ndarray[DTYPE_t, ndim = 2] ranges_1, np.ndarray[DTYPE_t, ndim=2] ranges_2, int n ):
k = 0
for i in range(n):
for j in range(len(ranges_1)):
r1 = ranges_1[j]
a = r1[0]
b = r1[1]
c = r1[2]
for f in range(len(ranges_2)):
r2 = ranges_2[f]
d = r2[0]
e = r2[1]
k = (a b c d e)/(d e)
return k
每個 10_000 個外部回圈大約需要 5 秒。所以我然后嘗試展平陣列,因為我知道另一個軸上的維度,所以訪問這樣的專案:
import numpy as np
cimport numpy as np
cimport cython
ctypedef np.int_t DTYPE_t
def test_func_flattened(np.ndarray[DTYPE_t, ndim = 1] ranges_1_, np.ndarray[DTYPE_t, ndim=1] ranges_2_, int n ):
k = 0
for i in range(n):
for j in range(0, len(ranges_1_), 3):
a = ranges_1_[j]
b = ranges_1_[j 1]
c = ranges_1_[j 2]
for f in range(0, len(ranges_2_), 2):
d = ranges_2_[f]
e = ranges_2_[f 1]
k = (a b c d e)/(d e)
return k
但這根本沒有提速。執行 10_000 的單次迭代的時間似乎太高了,考慮到單次迭代在回圈內只有 12_000_000 次操作。我還嘗試在 Cython 和 python 中實作一個更簡單的示例,然后使用 numba 進行編譯:
import numpy as np
cimport numpy as np
cimport cython
ctypedef np.int_t DTYPE_t
def test_1(int n ):
cdef k = 0
cdef a = 0
for i in range(n):
a = i 1
return a
在 n = 1_000_000_000 的情況下,這需要 15 秒才能運行。
而與numba:
def test_1_python(n ):
k = 0
a = 0
for i in range(n):
if i % 2 == 0:
a = a 1
else:
a = a - 1
return a
test_1_numba= numba.jit(test_1_python)
%%time
test_1_numba(120_000_000_000)
n = 120bln 的完整運行大約需要 6 秒,(盡管里面的函式更簡單)這意味著它將比 Cython 快 500 倍,這可能嗎?
我是 Cython 的新手,所以我可能遺漏了一些明顯的東西,但是由于 numba 版本(沒有陣列訪問)要快得多,我認為速度上的差異可能來自與訪問陣列中的專案相關的開銷。
這是一個錯誤的假設嗎?
如果不是,那么在 Cython 中回圈二維整數串列的最佳方法是什么?
uj5u.com熱心網友回復:
您在基準測驗中測量的主要是編譯工件和開銷。
首先,Cython 使用 Python 堆疊首選的安裝在您機器上的默認編譯器。在 Linux 上,它應該是 GCC。在 Windows 上,如果安裝了它肯定是 MSVC,否則是 MinGW(如果有)。同時,Numba 基于 LLVM-Lite,它基于 LLVM 堆疊,如 Clang。因此,在您的情況下,很可能使用了不同的編譯器,從而導致不同的二進制檔案具有不同的性能。如果你想做一個公平的基準測驗,你需要使用 Clang 來構建你的 Cython 程式。
此外,Cython 的默認優化-O2是-O3針對 Numba 的。前者不應該啟用自動矢量化,而后者應該啟用(這取決于目標編譯器——較新版本的 GCC 會改變這種行為)。此外,默認情況下,Cython 不啟用特定于機器的非便攜式優化(因為可以為其他機器打包二進制檔案,例如使用 pip)。這意味著 Cython 默認只能在 x86-64 處理器上使用舊的 SSE2 SIMD 指令集。同時,LLVM-JIT 可以使用更快的 AVX2/AVX-512 SIMD 指令集。您需要使用 Cython 手動啟用此類優化,以使基準測驗公平(即-march=native在 GCC/Clang 上)。
事實上,在我的 x86-64 主流 Intel 機器上,Numba 確實使用了 AVX2 指令集,而 Cython 并沒有在你最后的基準測驗中使用。例如,這是 Numba JIT 生成的主回圈:
.LBB0_7:
vptestnmq %ymm5, %ymm4, %k1
vpblendmq %ymm5, %ymm6, %ymm18 {%k1}
vpaddq %ymm0, %ymm18, %ymm0
vpaddq %ymm1, %ymm18, %ymm1
vpaddq %ymm2, %ymm18, %ymm2
vpaddq %ymm3, %ymm18, %ymm3
vpaddq %ymm16, %ymm4, %ymm18
vptestnmq %ymm5, %ymm18, %k1
vpblendmq %ymm5, %ymm6, %ymm18 {%k1}
vpaddq %ymm0, %ymm18, %ymm0
vpaddq %ymm1, %ymm18, %ymm1
vpaddq %ymm2, %ymm18, %ymm2
vpaddq %ymm3, %ymm18, %ymm3
vpaddq %ymm17, %ymm4, %ymm4
addq $-2, %rdx
jne .LBB0_7
對于a = i 1在回圈中進行的基準測驗,它是有缺陷的,因為一個好的編譯器可以優化整個回圈(即洗掉它)并只用一個賦值替換它,因為只有最后一次迭代很重要。事實上,同樣的事情也適用于k = (a b c d e)/(d e):只有最后一次迭代很重要。甚至沒有使用變數iof 。for i in range(n)Clang 和 GCC 經常做這樣的優化。
最后,如果修改初始代碼的速度以計算在實際用例中有意義的東西并且使用多個執行緒,那么它的速度將受到記憶體限制。
請注意,除法非常昂貴,您可以預先計算倒數,以便在主回圈中執行乘法運算。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/474466.html
上一篇:NasaAPI傳遞資料太慢了
下一篇:比較多個不同長度的numpy陣列
