Tôi thường nhận được hiệu suất tốt từ chức năng einsum của numpy (và tôi thích cú pháp của nó). @ Ophion của câu trả lời cho this question cho thấy rằng - đối với các trường hợp được kiểm tra - einsum luôn tốt hơn các chức năng "tích hợp sẵn" (đôi khi một chút, đôi khi rất nhiều). Nhưng tôi chỉ gặp phải một trường hợp mà einsum chậm hơn nhiều. Hãy xem xét các chức năng tương đương như sau:Tại sao einsum của numpy lại chậm hơn các chức năng tích hợp của 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)
tôi mong đợi func_einsum
để chạy nhanh nhất nhưng đó không phải là những gì tôi gặp phải. Chạy trên một CPU quad-core với hyperthreading, numpy phiên bản 1.9.0.dev-7ae0206, và đa luồng với OpenBLAS, tôi nhận được kết quả như sau:
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
Khi tôi tăng K
-200, sự khác biệt là cực đoan hơn :
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
Ai đó có thể giải thích tại sao einsum chậm hơn nhiều ở đây?
Nếu vấn đề, đây là cấu hình NumPy tôi:
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
Tôi đã nhận thấy điều tương tự khi so sánh 'np.einsum' với' np.tensordot'. Tôi nghi ngờ rằng đây có thể chỉ là giá bạn trả cho tổng quát - 'np.dot' gọi các chương trình con BLAS (' dgemm', vv) được tối ưu hóa rất cao cho trường hợp đặc biệt của các sản phẩm chấm giữa hai ma trận, trong khi 'np.einsum 'giao dịch với tất cả các loại kịch bản có khả năng liên quan đến nhiều ma trận đầu vào. Tôi không chắc chắn về các chi tiết chính xác, nhưng tôi nghi ngờ rằng sẽ khó thiết kế 'np.einsum' để sử dụng BLAS tối ưu trong tất cả các trường hợp như vậy. –