因此,我正在尝试在@njit
中将向量化和由numba
支持的for循环相结合来提高性能(我目前正在使用numba 0.45.1
)。令人失望的是,我发现它实际上比我的代码中的纯嵌套循环实现要慢。
这是我的代码:
import numpy as np
from numba import njit
@njit
def func3(arr_in, win_arr):
n = arr_in.shape[0]
win_len = len(win_arr)
result = np.full((n, win_len), np.nan)
alpha_arr = 2 / (win_arr + 1)
e = np.full(win_len, arr_in[0])
w = np.ones(win_len)
two_index = np.nonzero(win_arr <= 2)[0][-1]+1
result[0, :two_index] = arr_in[0]
for i in range(1, n):
w = w + (1-alpha_arr)**i
e = e*(1-alpha_arr) + arr_in[i]
result[i,:] = e /w
return result
@njit
def func4(arr_in, win_arr):
n = arr_in.shape[0]
win_len = len(win_arr)
result = np.full((n, win_len), np.nan)
alpha_arr = 2 / (win_arr + 1)
e = np.full(win_len, arr_in[0])
w = np.ones(win_len)
two_index = np.nonzero(win_arr <= 2)[0][-1]+1
result[0, :two_index] = arr_in[0]
for i in range(1, n):
for col in range(len(win_arr)):
w[col] = w[col] + (1-alpha_arr[col])**i
e[col] = e[col]*(1-alpha_arr[col]) + arr_in[i]
result[i,col] = e[col] /w[col]
return result
if __name__ == '__main__':
np.random.seed(0)
data_size = 200000
winarr_size = 1000
data = np.random.uniform(0,1000, size = data_size)+29000
win_array = np.arange(1, winarr_size+1)
abc_test3= func3(data, win_array)
abc_test4= func4(data, win_array)
print(np.allclose(abc_test3, abc_test4, equal_nan = True))
我使用以下配置对这两个函数进行了基准测试:
(data_size,winarr_size)
= (200000,100), (200000,200),(200000,1000), (200000,2000), (20000,10000), (2000,100000)
。
并发现,纯嵌套循环实现(func4
)始终比混合向量化(func3
的for循环实现更快(约快2%至5%)。
我的问题如下:
1)要进一步提高代码速度需要更改什么?
2)为什么函数的矢量化版本的计算时间随win_arr
的大小线性增长?我认为矢量化应该做到这一点,以便无论矢量大小如何,操作速度都是恒定的,但是在这种情况下显然不成立。
3)是否有一般条件下矢量化运算的计算时间仍将随输入大小线性增长?
答案 0 :(得分:3)
似乎您误解了“向量化”的含义。向量化意味着您编写的代码可以在标量数组上操作,即使它们是标量-但这只是代码的外观,与性能无关。
在Python / NumPy世界中,矢量化还具有这样的含义,即与循环代码相比,矢量化操作中循环的开销(通常)很多更小。但是,矢量化的代码仍然必须执行循环(即使已隐藏在库中)!
此外,如果您使用numba编写循环,numba将对其进行编译并创建执行(通常)与矢量化NumPy代码一样快的快速代码。这意味着在numba函数内部,矢量化和非矢量化代码之间没有明显的性能差异。
这样应该可以回答您的问题:
2)为什么函数的矢量化版本的计算时间随win_arr的大小线性增长?我认为矢量化应该做到这一点,以便无论矢量大小如何,操作速度都是恒定的,但是在这种情况下显然不成立。
它线性增长,因为它仍然需要迭代。在矢量化代码中,循环只是隐藏在库例程中。
3)是否有一般条件下矢量化运算的计算时间仍将随输入大小线性增长?
否。
您还询问了如何使它更快。
已经提到的注释可以并行处理:
import numpy as np
import numba as nb
@nb.njit(parallel=True)
def func6(arr_in, win_arr):
n = arr_in.shape[0]
win_len = len(win_arr)
result = np.full((n, win_len), np.nan)
alpha_arr = 2 / (win_arr + 1)
e = np.full(win_len, arr_in[0])
w = np.ones(win_len)
two_index = np.nonzero(win_arr <= 2)[0][-1]+1
result[0, :two_index] = arr_in[0]
for i in range(1, n):
for col in nb.prange(len(win_arr)):
w[col] = w[col] + (1-alpha_arr[col])**i
e[col] = e[col] * (1-alpha_arr[col]) + arr_in[i]
result[i,col] = e[col] /w[col]
return result
这使代码在我的计算机上更快(4核)。
但是,还有一个问题是您的算法可能在数值上不稳定。当您将(1-alpha_arr[col])**i
提升至十万次幂时,它会在某些时候下溢:
>>> alpha = 0.01
>>> for i in [1, 10, 100, 1_000, 10_000, 50_000, 100_000, 200_000]:
... print((1-alpha)**i)
0.99
0.9043820750088044
0.3660323412732292
4.317124741065786e-05
2.2487748498162805e-44
5.750821364590612e-219
0.0 # <-- underflow
0.0
答案 1 :(得分:1)
对于诸如pow,除法等复杂的数学运算,请三思。如果您可以通过简单的运算(例如乘法,加法和减法)来替换它们,那么始终值得一试。
请注意,将alpha与其自身重复乘以仅在代数上与直接使用幂运算进行代数相同。由于这是数值数学,因此结果可能会有所不同。
还要避免不必要的临时数组。
第一次尝试
@nb.njit(error_model="numpy",parallel=True)
def func5(arr_in, win_arr):
#filling the whole array with NaNs isn't necessary
result = np.empty((win_arr.shape[0],arr_in.shape[0]))
for col in range(win_arr.shape[0]):
result[col,0]=np.nan
two_index = np.nonzero(win_arr <= 2)[0][-1]+1
result[:two_index,0] = arr_in[0]
for col in nb.prange(win_arr.shape[0]):
alpha=1.-(2./ (win_arr[col] + 1.))
alpha_exp=alpha
w=1.
e=arr_in[0]
for i in range(1, arr_in.shape[0]):
w+= alpha_exp
e = e*alpha + arr_in[i]
result[col,i] = e/w
alpha_exp*=alpha
return result.T
第二次尝试(避免下溢)
@nb.njit(error_model="numpy",parallel=True)
def func7(arr_in, win_arr):
#filling the whole array with NaNs isn't necessary
result = np.empty((win_arr.shape[0],arr_in.shape[0]))
for col in range(win_arr.shape[0]):
result[col,0]=np.nan
two_index = np.nonzero(win_arr <= 2)[0][-1]+1
result[:two_index,0] = arr_in[0]
for col in nb.prange(win_arr.shape[0]):
alpha=1.-(2./ (win_arr[col] + 1.))
alpha_exp=alpha
w=1.
e=arr_in[0]
for i in range(1, arr_in.shape[0]):
w+= alpha_exp
e = e*alpha + arr_in[i]
result[col,i] = e/w
if np.abs(alpha_exp)>=1e-308:
alpha_exp*=alpha
else:
alpha_exp=0.
return result.T
时间
%timeit abc_test3= func3(data, win_array)
7.17 s ± 45.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit abc_test4= func4(data, win_array)
7.13 s ± 13.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
#from MSeifert answer (parallelized)
%timeit abc_test6= func6(data, win_array)
3.42 s ± 153 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit abc_test5= func5(data, win_array)
1.22 s ± 22.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit abc_test7= func7(data, win_array)
238 ms ± 5.55 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)