Di solito ho ottenuto una buona performance dalla funzione di einsum di Numpy (e mi piace che sia sintassi). @ La risposta di Ophion a this question mostra che - per i casi testati - einsum supera costantemente le funzioni "built-in" (a volte un po ', a volte molto spesso). Ma ho appena incontrato un caso in cui l'einsum è molto più lento. Considerate le seguenti funzioni equivalenti:Perché l'einsum di Numpy è più lento delle funzioni incorporate di numpy?
(M, K) = (1000000, 20)
C = np.random.rand(K, K)
X = np.random.rand(M, K)
def func_dot(C, X):
Y = X.dot(C)
return np.sum(Y * X, axis=1)
def func_einsum(C, X):
return np.einsum('ik,km,im->i', X, C, X)
def func_einsum2(C, X):
# Like func_einsum but break it into two steps.
A = np.einsum('ik,km', X, C)
return np.einsum('ik,ik->i', A, X)
mi aspettavo func_einsum
a correre più veloce, ma non è questo che ho incontrato. Eseguito su una CPU quad-core con hyperthreading, versione NumPy 1.9.0.dev-7ae0206, e multithreading con OpenBLAS, ottengo i seguenti risultati:
In [2]: %time y1 = func_dot(C, X)
CPU times: user 320 ms, sys: 312 ms, total: 632 ms
Wall time: 209 ms
In [3]: %time y2 = func_einsum(C, X)
CPU times: user 844 ms, sys: 0 ns, total: 844 ms
Wall time: 842 ms
In [4]: %time y3 = func_einsum2(C, X)
CPU times: user 292 ms, sys: 44 ms, total: 336 ms
Wall time: 334 ms
Quando ho aumentare K
a 200, le differenze sono più estreme :
In [2]: %time y1= func_dot(C, X)
CPU times: user 4.5 s, sys: 1.02 s, total: 5.52 s
Wall time: 2.3 s
In [3]: %time y2= func_einsum(C, X)
CPU times: user 1min 16s, sys: 44 ms, total: 1min 16s
Wall time: 1min 16s
In [4]: %time y3 = func_einsum2(C, X)
CPU times: user 15.3 s, sys: 312 ms, total: 15.6 s
Wall time: 15.6 s
Qualcuno può spiegare perché l'einsum è molto più lento qui?
Se è importante, qui è il mio config NumPy:
In [6]: np.show_config()
lapack_info:
libraries = ['openblas']
library_dirs = ['/usr/local/lib']
language = f77
atlas_threads_info:
libraries = ['openblas']
library_dirs = ['/usr/local/lib']
define_macros = [('ATLAS_WITHOUT_LAPACK', None)]
language = c
include_dirs = ['/usr/local/include']
blas_opt_info:
libraries = ['openblas']
library_dirs = ['/usr/local/lib']
define_macros = [('ATLAS_INFO', '"\\"None\\""')]
language = c
include_dirs = ['/usr/local/include']
atlas_blas_threads_info:
libraries = ['openblas']
library_dirs = ['/usr/local/lib']
define_macros = [('ATLAS_INFO', '"\\"None\\""')]
language = c
include_dirs = ['/usr/local/include']
lapack_opt_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/usr/local/lib']
define_macros = [('ATLAS_WITHOUT_LAPACK', None)]
language = f77
include_dirs = ['/usr/local/include']
lapack_mkl_info:
NOT AVAILABLE
blas_mkl_info:
NOT AVAILABLE
mkl_info:
NOT AVAILABLE
Ho notato la stessa cosa confrontando 'np.einsum' con' np.tensordot'. Sospetto che questo possa essere solo il prezzo che si paga per la generalità - 'np.dot' chiama le subroutine BLAS (' dgemm' ecc.) Che sono molto ottimizzate per il caso speciale di prodotti punto tra due matrici, mentre 'np.einsum 'si occupa di tutti i tipi di scenari potenzialmente coinvolgenti più matrici di input. Non sono sicuro dei dettagli esatti, ma ho il sospetto che sarebbe difficile progettare 'np.einsum' per fare un uso ottimale di BLAS in tutti questi casi. –