我是 Numba 的新手,我正在嘗試使用 Numba(版本 0.54.1)在 Python 中實作一個舊的 Fortran 代碼,但是當我添加parallel = True程式時,它實際上變慢了。我的程式非常簡單:我在 L x L 網格中更改位置 x 和 y,并對網格中的每個位置進行求和
import numpy as np
import numba as nb
@nb.njit(parallel=True)
def lyapunov_grid(x_grid, y_grid, k, N):
L = len(x_grid)
lypnv = np.zeros((L, L))
for ii in nb.prange(L):
for jj in range(L):
x = x_grid[ii]
y = y_grid[jj]
beta0 = 0
sumT11 = 0
for j in range(N):
y = (y - k*np.sin(x)) % (2*np.pi)
x = (x y) % (2*np.pi)
J = np.array([[1.0, -k*np.cos(x)], [1.0, 1.0 - k*np.cos(x)]])
beta = np.arctan((-J[1,0]*np.cos(beta0) J[1,1]*np.sin(beta0))/(J[0,0]*np.cos(beta0) - J[0,1]*np.sin(beta0)))
T11 = np.cos(beta0)*(J[0,0]*np.cos(beta) - J[1,0]*np.sin(beta)) - np.sin(beta0)*(J[0,1]*np.cos(beta) - J[1,1]*np.sin(beta))
sumT11 = np.log(abs(T11))/np.log(2)
beta0 = beta
lypnv[ii, jj] = sumT11/N
return lypnv
# Compile
_ = lyapunov_grid(np.linspace(0, 1, 10), np.linspace(0, 1, 10), 1, 10)
# Parameters
N = int(1e3)
L = 128
pi = np.pi
k = 1.5
# Limits of the phase space
x0 = -pi
xf = pi
y0 = -pi
yf = pi
# Grid positions
x = np.linspace(x0, xf, L, endpoint=True)
y = np.linspace(y0, yf, L, endpoint=True)
lypnv = lyapunov_grid(x, y, k, N)
有了parallel=False它需要大約8秒運行,但與parallel=True大約需要14秒。我還使用來自https://github.com/animator/mandelbrot-numba 的另一個代碼進行了測驗,在這種情況下,并行化有效。
import math
import numpy as np
import numba as nb
WIDTH = 1000
MAX_ITER = 1000
@nb.njit(parallel=True)
def mandelbrot(width, max_iter):
pixels = np.zeros((width, width, 3), dtype=np.uint8)
for y in nb.prange(width):
for x in range(width):
c0 = complex(3.0*x/width - 2, 3.0*y/width - 1.5)
c = 0
for i in range(1, max_iter):
if abs(c) > 2:
log_iter = math.log(i)
pixels[y, x, :] = np.array([int(255*(1 math.cos(3.32*log_iter))/2),
int(255*(1 math.cos(0.774*log_iter))/2),
int(255*(1 math.cos(0.412*log_iter))/2)],
dtype=np.uint8)
break
c = c * c c0
return pixels
# compile
_ = mandelbrot(WIDTH, 10)
calcpixels = mandelbrot(WIDTH, MAX_ITER)
uj5u.com熱心網友回復:
一個主要問題是第二個函式呼叫再次編譯該函式。實際上,提供的引數的型別發生了變化:在第一次呼叫中,第三個引數是一個整數(int轉換為 a np.int_),而在第二次呼叫中,第三個引數 ( k) 是一個浮點數(float轉換為 a np.float64)。Numba 為不同的引數型別重新編譯函式,因為它們是從引數的型別推匯出來的,并且它不知道您要np.float64為第三個引數使用型別(因為第一次為np.int_型別編譯函式)。解決問題的一個簡單解決方案是將第一個呼叫更改為:
_ = lyapunov_grid(np.linspace(0, 1, 10), np.linspace(0, 1, 10), 1.0, 10)
但是,這不是解決問題的可靠方法。您可以為 Numba 指定引數型別,以便在宣告時編譯函式。這也消除了人為呼叫函式(使用無用引數)的需要。
@nb.njit('float64[:,:](float64[::1], float64[::1], float64, float64)', parallel=True)
請注意,(J[0,0]*np.cos(beta0) - J[0,1]*np.sin(beta0))第一次為零導致除以 0。
另一個主要問題來自回圈中許多小陣列的分配,導致標準分配器的爭用(有關更多資訊,請參閱此帖子)。雖然 Numba 理論上可以優化它(即用區域變數替換陣列),但實際上并沒有,導致巨大的減速和爭用。希望在您的情況下,您不需要實際創建陣列。最后,您只能在包含回圈中創建它并在最內層回圈中修改它。這是優化后的代碼:
@nb.njit('float64[:,:](float64[::1], float64[::1], float64, float64)', parallel=True)
def lyapunov_grid(x_grid, y_grid, k, N):
L = len(x_grid)
lypnv = np.zeros((L, L))
for ii in nb.prange(L):
J = np.ones((2, 2), dtype=np.float64)
for jj in range(L):
x = x_grid[ii]
y = y_grid[jj]
beta0 = 0
sumT11 = 0
for j in range(N):
y = (y - k*np.sin(x)) % (2*np.pi)
x = (x y) % (2*np.pi)
J[0, 1] = -k*np.cos(x)
J[1, 1] = 1.0 - k*np.cos(x)
beta = np.arctan((-J[1,0]*np.cos(beta0) J[1,1]*np.sin(beta0))/(J[0,0]*np.cos(beta0) - J[0,1]*np.sin(beta0)))
T11 = np.cos(beta0)*(J[0,0]*np.cos(beta) - J[1,0]*np.sin(beta)) - np.sin(beta0)*(J[0,1]*np.cos(beta) - J[1,1]*np.sin(beta))
sumT11 = np.log(abs(T11))/np.log(2)
beta0 = beta
lypnv[ii, jj] = sumT11/N
return lypnv
這是在舊的 2 核機器(具有 4 個硬體執行緒)上的結果:
Original sequential: 15.9 s
Original parallel: 11.9 s
Fix-build sequential: 15.7 s
Fix-build parallel: 10.1 s
Optimized sequential: 2.73 s
Optimized parallel: 0.94 s
優化的實作比其他實作要快得多。與原始版本相比,并行優化版本的擴展性非常好(比順序版本快 2.9 倍)。最后,最佳版本比原始并行版本快 12 倍左右。我希望在具有更多內核的最新機器上進行更快的計算。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/394327.html
上一篇:性能:在迭代時根據選項更改資料
