Давайте начнем с трех массивов dtype=np.double
. Сроки выполняются на процессоре Intel, используя numpy 1.7.1, скомпилированный с icc
и связанный с intel mkl
. Для проверки таймингов также использовался процессор AMD c numpy 1.6.1, скомпилированный с gcc
без mkl
. Обратите внимание, что шкала таймингов почти линейно зависит от размера системы и не связана с небольшими накладными расходами, вызванными операторами nump функций if
. Эти различия будут отображаться в микросекундах не в миллисекундах:
arr_1D=np.arange(500,dtype=np.double)
large_arr_1D=np.arange(100000,dtype=np.double)
arr_2D=np.arange(500**2,dtype=np.double).reshape(500,500)
arr_3D=np.arange(500**3,dtype=np.double).reshape(500,500,500)
Сначала рассмотрим функцию np.sum
:
np.all(np.sum(arr_3D)==np.einsum('ijk->',arr_3D))
True
%timeit np.sum(arr_3D)
10 loops, best of 3: 142 ms per loop
%timeit np.einsum('ijk->', arr_3D)
10 loops, best of 3: 70.2 ms per loop
Пауэрс:
np.allclose(arr_3D*arr_3D*arr_3D,np.einsum('ijk,ijk,ijk->ijk',arr_3D,arr_3D,arr_3D))
True
%timeit arr_3D*arr_3D*arr_3D
1 loops, best of 3: 1.32 s per loop
%timeit np.einsum('ijk,ijk,ijk->ijk', arr_3D, arr_3D, arr_3D)
1 loops, best of 3: 694 ms per loop
Внешнее изделие:
np.all(np.outer(arr_1D,arr_1D)==np.einsum('i,k->ik',arr_1D,arr_1D))
True
%timeit np.outer(arr_1D, arr_1D)
1000 loops, best of 3: 411 us per loop
%timeit np.einsum('i,k->ik', arr_1D, arr_1D)
1000 loops, best of 3: 245 us per loop
Все вышеперечисленное в два раза быстрее с np.einsum
. Это должны быть яблоки для сравнения яблок, так как все специально относится к dtype=np.double
. Я ожидал бы ускорения в такой операции:
np.allclose(np.sum(arr_2D*arr_3D),np.einsum('ij,oij->',arr_2D,arr_3D))
True
%timeit np.sum(arr_2D*arr_3D)
1 loops, best of 3: 813 ms per loop
%timeit np.einsum('ij,oij->', arr_2D, arr_3D)
10 loops, best of 3: 85.1 ms per loop
Einsum, по-видимому, по крайней мере в два раза быстрее для np.inner
, np.outer
, np.kron
и np.sum
независимо от выбора axes
. Основное исключение - np.dot
, поскольку оно вызывает DGEMM из библиотеки BLAS. Итак, почему np.einsum
быстрее, чем другие эквивалентные функции numpy?
Случай DGEMM для полноты:
np.allclose(np.dot(arr_2D,arr_2D),np.einsum('ij,jk',arr_2D,arr_2D))
True
%timeit np.einsum('ij,jk',arr_2D,arr_2D)
10 loops, best of 3: 56.1 ms per loop
%timeit np.dot(arr_2D,arr_2D)
100 loops, best of 3: 5.17 ms per loop
Ведущая теория - комментарий @sebergs, что np.einsum
может использовать SSE2, но numpy ufuncs не будет до numpy 1.8 (см. изменить журнал). Я считаю, что это правильный ответ, но он не смог его подтвердить. Некоторое ограниченное доказательство можно найти, изменив тип входного массива и наблюдая разницу в скорости и тот факт, что не все наблюдают одни и те же тенденции в таймингах.