簡體   English   中英

在 numpy 中將小矩陣與標量相乘的最有效方法

[英]Most efficient way to multiply a small matrix with a scalar in numpy

我有一個程序,其主要性能瓶頸涉及將一個維度為 1 的矩陣和另一個大維度的矩陣相乘,例如 1000:

large_dimension = 1000

a = np.random.random((1,))
b = np.random.random((1, large_dimension))

c = np.matmul(a, b)

換句話說,將矩陣b與標量a[0]相乘。

我正在尋找最有效的方法來計算這個,因為這個操作重復了數百萬次。

我測試了這兩種簡單方法的性能,它們實際上是等效的:

%timeit np.matmul(a, b)
>> 1.55 µs ± 45.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit a[0] * b
>> 1.77 µs ± 34.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

有沒有更有效的方法來計算這個?

  • 注意:我無法將這些計算移至 GPU,因為該程序正在使用多處理並且許多此類計算是並行完成的。

在這種情況下,使用逐元素乘法可能更快,但您看到的時間主要是 Numpy 的開銷(從 CPython 解釋器調用 C 函數、包裝/解包類型、進行檢查、執行操作、數組分配, ETC。)。

因為這個操作重復了數百萬次

這就是問題。 確實,CPython解釋器在以低延遲做事方面非常糟糕。 當您在 Numpy 類型上工作時尤其如此,因為調用 C 代碼並且執行瑣碎操作的檢查比在純 ZA7F5F35426B927411FC9231B563821773 Z 編譯代碼慢得多,這也慢得多。 如果你真的需要這個,並且你不能使用 Numpy對你的代碼進行矢量化(因為你有一個循環迭代時間步長),那么你就不要使用 CPython,或者至少不要使用純 Python 代碼。 相反,您可以使用NumbaCython來減輕執行 C 調用、包裝類型等的影響。如果這還不夠,那么您將需要編寫本機 C/C++ 代碼(或任何類似語言),除非您找到確切的專用 Python package正是為您做的。 請注意,Numba 僅在適用於本機類型或 Numpy arrays(包含本機類型)時才快速。 如果您使用大量純 Python 類型並且您不想重寫代碼,那么您可以嘗試PyPy JIT。


這是 Numba 中的一個簡單示例,該示例避免了專門為解決您的特定情況而編寫的新數組(以及許多 Numpy 內部檢查和調用)的(昂貴的)創建/分配:

@nb.njit('void(float64[::1],float64[:,::1],float64[:,::1])')
def fastMul(a, b, out):
    val = a[0]
    for i in range(b.shape[1]):
        out[0,i] = b[0,i] * val

res = np.empty(b.shape, dtype=b.dtype)
%timeit fastMul(a, b, res)
# 397 ns ± 0.587 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

在撰寫本文時,此解決方案比其他所有解決方案都快。 由於大部分時間都花在調用 Numba 和執行一些內部檢查上,因此直接對包含迭代循環的 function 使用 Numba 應該會產生更快的代碼。

import numpy as np
import numba

def matmult_numpy(matrix, c):
    return np.matmul(c, matrix)

@numba.jit(nopython=True)
def matmult_numba(matrix, c):
    return c*matrix

if __name__ == "__main__":
    large_dimension = 1000
    a = np.random.random((1, large_dimension))
    c = np.random.random((1,))

使用 Numba 的速度大約提高了 3 倍。 Numba cognoscenti 可以通過將參數“c”顯式轉換為標量來做得更好

檢查:結果

%timeit matmult_numpy(a, c)每個循環2.32 µs ± 50 ns(平均值 ± 標准偏差,7 次運行,每次 100000 次循環)

%timeit matmult_numba(a, c)每個循環763 ns ± 6.67 ns(平均值 ± 標准偏差,7 次運行,每次 1000000 次循環)

large_dimension = 1000

a = np.random.random((1,))
B = np.random.random((1, large_dimension))

%timeit np.matmul(a, B)
5.43 µs ± 22 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit a[0] * B
5.11 µs ± 6.92 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

只使用浮動

%timeit float(a[0]) * B
3.48 µs ± 26.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

為避免 memory 分配使用“緩沖區”

buffer = np.empty_like(B)

%timeit np.multiply(float(a[0]), B, buffer)
2.96 µs ± 37.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

為避免不必要的獲取屬性,請使用“別名”

mul = np.multiply

%timeit mul(float(a[0]), B, buffer)
2.73 µs ± 12.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

而且我根本不推薦使用 numpy 標量,因為如果你避免它,計算會更快

a_float = float(a[0])

%timeit mul(a_float, B, buffer)
1.94 µs ± 5.74 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

此外,如果有可能,則在循環外初始化緩沖區一次(當然,如果你有類似循環的東西:)

rng = range(1000)

%%timeit
for i in rng:
    pass
24.4 µs ± 1.21 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%%timeit
for i in rng:
    mul(a_float, B, buffer)
1.91 ms ± 2.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

所以,

“最佳迭代時間” = (1.91 - 0.02) / 1000 => 1.89 (µs)

“加速” = 5.43 / 1.89 = 2.87

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM