當我使用 numba 中的 njit 并行運行該程式時,我注意到使用多個執行緒并沒有什么不同。事實上,從 1-5 個執行緒開始,時間會更快(這是預期的),但之后時間會變慢。為什么會這樣?
from numba import njit,prange,set_num_threads,get_num_threads
import numpy as np
@njit(parallel=True)
def test(x,y):
z=np.empty((x.shape[0],x.shape[0]),dtype=np.float64)
for i in prange(x.shape[0]):
for j in range(x.shape[0]):
z[i,j]=x[i,j]*y[i,j]
return z
x=np.random.rand(10000,10000)
y=np.random.rand(10000,10000)
for i in range(16):
set_num_threads(i 1)
print("Number of threads :",get_num_threads())
%timeit -r 1 -n 10 test(x,y)
Number of threads : 1
234 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 2
178 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 3
168 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 4
161 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 5
148 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 6
152 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 7
152 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 8
153 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 9
154 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 10
156 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 11
158 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 12
157 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 13
158 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 14
160 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 15
160 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
Number of threads : 16
161 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 10 loops each)
我在具有 8 個內核和 16 個執行緒的 cpu 中的 Jupyter Notebook (anaconda) 中對此進行了測驗。
uj5u.com熱心網友回復:
代碼是受記憶體限制的,因此 RAM 中只有幾個核心就飽和了。
實際上,由于 x86-64 處理器上的寫入分配快取策略(在這種情況下必須讀取寫入的快取行) ,z[i,j]=x[i,j]*y[i,j]導致兩個 8 位元組的記憶體負載,一個 8 位元組的存盤和一個額外的 8 位元組負載。這意味著每次回圈迭代加載/存盤 32 個位元組,而只需要進行 1 次乘法運算。現代主流 (x86-64) 處理器可以進行 2x4 雙精度 FP 乘法/周期,并在 3-5 GHz 下運行(實際上,英特爾服務器處理器可以進行 2x8 DP FP 乘法/周期)。同時一臺好的主流PC只能達到40-60 GiB/s,高性能服務器200-350 GiB/s。
沒有辦法在 Numba 中加速這樣的記憶體系結代碼。C/C 代碼可以通過避免寫入分配來稍微改善這一點(最高快 1.33 倍)。最好的解決方案是盡可能在更小的塊上進行操作,并合并計算步驟,以便在每個步驟中應用更多的 FP 操作。
實際上,眾所周知,與處理器的計算能力相比,RAM 的速度增長緩慢。這個問題在幾十年前就已經確定了,隨著時間的推移,兩者之間的差距仍然越來越大。這個問題被稱為“記憶墻”。這在未來不會更好(至少不太可能是這樣)。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/496634.html
