numpy의 einsum이 numpy의 내장 함수보다 빠른 이유는 무엇입니까?
3 개의 dtype=np.double
. 타이밍은 인텔의 .NET Framework로 컴파일 icc
되고 연결된 numpy 1.7.1을 사용하여 인텔 CPU에서 수행됩니다 mkl
. numpy 1.6.1 gcc
없이 컴파일 된 AMD CPU mkl
도 타이밍을 확인하는 데 사용되었습니다. 타이밍은 시스템 크기에 따라 거의 선형 적으로 확장되며 numpy 함수 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
는 BLAS 라이브러리에서 DGEMM을 호출하는 것입니다. 그렇다면 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
주요 이론은 SSE2를np.einsum
사용할 수있는 @sebergs 주석에서 나왔지만 numpy의 ufuncs는 numpy 1.8 ( 변경 로그 참조)까지는 그렇지 않습니다 . 나는 이것이 정답이라고 생각하지만 확인 하지 못했습니다 . 입력 배열의 dtype을 변경하고 속도 차이를 관찰하고 모든 사람이 타이밍에서 동일한 경향을 관찰하지 않는다는 사실을 관찰하면 제한된 증거를 찾을 수 있습니다.
우선, numpy 목록에서 이것에 대해 과거에 많은 논의가있었습니다. 예를 들어 다음을 참조하십시오. http://numpy-discussion.10968.n7.nabble.com/poor-performance-of-sum-with-sub-machine-word-integer-types-td41.html http : // numpy- discussion.10968.n7.nabble.com/odd-performance-of-sum-td3332.html
일부 einsum
는 새로운 사실로 귀결되며 캐시 정렬 및 기타 메모리 액세스 문제에 대해 더 나은 방법을 시도하고있는 반면, 이전의 numpy 기능 중 상당수는 고도로 최적화 된 것보다 쉽게 이식 가능한 구현에 중점을 둡니다. 그래도 추측하고 있습니다.
그러나 당신이하는 일 중 일부는 "사과 간"비교가 아닙니다.
@Jamie가 이미 말한 것 외에도 sum
배열에 더 적합한 누산기를 사용합니다.
예를 들어, sum
입력 유형을 확인하고 적절한 누산기를 사용하는 데 더주의해야합니다. 예를 들어 다음을 고려하십시오.
In [1]: x = 255 * np.ones(100, dtype=np.uint8)
In [2]: x
Out[2]:
array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255], dtype=uint8)
(가) 있습니다 sum
올바른 :
In [3]: x.sum()
Out[3]: 25500
einsum
잘못된 결과를 제공하는 동안 :
In [4]: np.einsum('i->', x)
Out[4]: 156
그러나 덜 제한된을 사용하는 경우 dtype
에도 예상 한 결과를 얻을 수 있습니다.
In [5]: y = 255 * np.ones(100)
In [6]: np.einsum('i->', y)
Out[6]: 25500.0
문서에 따르면 모든 ufunc가 SSE2를 사용해야하는 numpy 1.8이 출시되었으므로 SSE2에 대한 Seberg의 의견이 유효한지 다시 확인하고 싶었습니다.
테스트를 수행하기 위해 새로운 python 2.7 설치가 생성되었습니다 icc
. Ubuntu를 실행하는 AMD opteron 코어에서 표준 옵션을 사용하여 numpy 1.7 및 1.8이 컴파일되었습니다 .
이것은 1.8 업그레이드 전후의 테스트 실행입니다.
import numpy as np
import timeit
arr_1D=np.arange(5000,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)
print 'Summation test:'
print timeit.timeit('np.sum(arr_3D)',
'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
number=5)/5
print timeit.timeit('np.einsum("ijk->", arr_3D)',
'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
number=5)/5
print '----------------------\n'
print 'Power test:'
print timeit.timeit('arr_3D*arr_3D*arr_3D',
'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
number=5)/5
print timeit.timeit('np.einsum("ijk,ijk,ijk->ijk", arr_3D, arr_3D, arr_3D)',
'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
number=5)/5
print '----------------------\n'
print 'Outer test:'
print timeit.timeit('np.outer(arr_1D, arr_1D)',
'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
number=5)/5
print timeit.timeit('np.einsum("i,k->ik", arr_1D, arr_1D)',
'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
number=5)/5
print '----------------------\n'
print 'Einsum test:'
print timeit.timeit('np.sum(arr_2D*arr_3D)',
'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
number=5)/5
print timeit.timeit('np.einsum("ij,oij->", arr_2D, arr_3D)',
'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
number=5)/5
print '----------------------\n'
Numpy 1.7.1 :
Summation test:
0.172988510132
0.0934836149216
----------------------
Power test:
1.93524689674
0.839519000053
----------------------
Outer test:
0.130380821228
0.121401786804
----------------------
Einsum test:
0.979052495956
0.126066613197
Numpy 1.8 :
Summation test:
0.116551589966
0.0920487880707
----------------------
Power test:
1.23683619499
0.815982818604
----------------------
Outer test:
0.131808176041
0.127472200394
----------------------
Einsum test:
0.781750011444
0.129271841049
나는 이것이 SSE가 타이밍 차이에서 큰 역할을한다는 상당히 결정적이라고 생각합니다. 이러한 테스트를 반복하면 타이밍이 ~ 0.003 초에 불과하다는 점에 유의해야합니다. 나머지 차이점은이 질문에 대한 다른 답변에서 다루어야합니다.
이 타이밍이 무슨 일이 일어나고 있는지 설명한다고 생각합니다.
a = np.arange(1000, dtype=np.double)
%timeit np.einsum('i->', a)
100000 loops, best of 3: 3.32 us per loop
%timeit np.sum(a)
100000 loops, best of 3: 6.84 us per loop
a = np.arange(10000, dtype=np.double)
%timeit np.einsum('i->', a)
100000 loops, best of 3: 12.6 us per loop
%timeit np.sum(a)
100000 loops, best of 3: 16.5 us per loop
a = np.arange(100000, dtype=np.double)
%timeit np.einsum('i->', a)
10000 loops, best of 3: 103 us per loop
%timeit np.sum(a)
10000 loops, best of 3: 109 us per loop
따라서 기본적으로 np.sum
over를 호출 할 때 거의 일정한 3us 오버 헤드가 np.einsum
있으므로 기본적으로 빠르게 실행되지만 진행하는 데 조금 더 오래 걸립니다. 왜 그럴 수 있습니까? 내 돈은 다음과 같습니다.
a = np.arange(1000, dtype=object)
%timeit np.einsum('i->', a)
Traceback (most recent call last):
...
TypeError: invalid data type for einsum
%timeit np.sum(a)
10000 loops, best of 3: 20.3 us per loop
확실하지 무슨 일이 정확히 벌어지고 있지만, 그 보인다 np.einsum
곱셈 및 추가 할 추출 유형의 특정 기능에 몇 가지 검사를 생략하고, 직접 것입니다 *
및 +
표준 C 타입 만.
다차원 사례는 다르지 않습니다.
n = 10; a = np.arange(n**3, dtype=np.double).reshape(n, n, n)
%timeit np.einsum('ijk->', a)
100000 loops, best of 3: 3.79 us per loop
%timeit np.sum(a)
100000 loops, best of 3: 7.33 us per loop
n = 100; a = np.arange(n**3, dtype=np.double).reshape(n, n, n)
%timeit np.einsum('ijk->', a)
1000 loops, best of 3: 1.2 ms per loop
%timeit np.sum(a)
1000 loops, best of 3: 1.23 ms per loop
따라서 대부분의 지속적인 오버 헤드가 발생합니다.
numpy 1.16.4 업데이트 : Numpy의 기본 함수는 거의 모든 경우에 einsums보다 빠릅니다. einsum의 외부 변형과 sum23만이 비 einsum 버전보다 빠르게 테스트됩니다.
numpy의 기본 함수를 사용할 수 있다면 그렇게하십시오.
( 내 프로젝트 인 perfplot으로 만든 이미지 .)
플롯을 재현하는 코드 :
import numpy
import perfplot
def setup1(n):
return numpy.arange(n, dtype=numpy.double)
def setup2(n):
return numpy.arange(n ** 2, dtype=numpy.double).reshape(n, n)
def setup3(n):
return numpy.arange(n ** 3, dtype=numpy.double).reshape(n, n, n)
def setup23(n):
return (
numpy.arange(n ** 2, dtype=numpy.double).reshape(n, n),
numpy.arange(n ** 3, dtype=numpy.double).reshape(n, n, n)
)
def numpy_sum(a):
return numpy.sum(a)
def einsum_sum(a):
return numpy.einsum("ijk->", a)
perfplot.save(
"sum.png",
setup=setup3,
kernels=[numpy_sum, einsum_sum],
n_range=[2 ** k for k in range(10)],
logx=True,
logy=True,
title="sum",
)
def numpy_power(a):
return a * a * a
def einsum_power(a):
return numpy.einsum("ijk,ijk,ijk->ijk", a, a, a)
perfplot.save(
"power.png",
setup=setup3,
kernels=[numpy_power, einsum_power],
n_range=[2 ** k for k in range(9)],
logx=True,
logy=True,
)
def numpy_outer(a):
return numpy.outer(a, a)
def einsum_outer(a):
return numpy.einsum("i,k->ik", a, a)
perfplot.save(
"outer.png",
setup=setup1,
kernels=[numpy_outer, einsum_outer],
n_range=[2 ** k for k in range(13)],
logx=True,
logy=True,
title="outer",
)
def dgemm_numpy(a):
return numpy.dot(a, a)
def dgemm_einsum(a):
return numpy.einsum("ij,jk", a, a)
def dgemm_einsum_optimize(a):
return numpy.einsum("ij,jk", a, a, optimize=True)
perfplot.save(
"dgemm.png",
setup=setup2,
kernels=[dgemm_numpy, dgemm_einsum],
n_range=[2 ** k for k in range(13)],
logx=True,
logy=True,
title="dgemm",
)
def dot_numpy(a):
return numpy.dot(a, a)
def dot_einsum(a):
return numpy.einsum("i,i->", a, a)
perfplot.save(
"dot.png",
setup=setup1,
kernels=[dot_numpy, dot_einsum],
n_range=[2 ** k for k in range(20)],
logx=True,
logy=True,
title="dot",
)
def sum23_numpy(data):
a, b = data
return numpy.sum(a * b)
def sum23_einsum(data):
a, b = data
return numpy.einsum('ij,oij->', a, b)
perfplot.save(
"sum23.png",
setup=setup23,
kernels=[sum23_numpy, sum23_einsum],
n_range=[2 ** k for k in range(10)],
logx=True,
logy=True,
title="sum23",
)
'Programing' 카테고리의 다른 글
Python functools.는 클래스에 해당하는 것을 래핑합니다. (0) | 2020.11.09 |
---|---|
Gradle과 Maven의 차이점은 무엇입니까? (0) | 2020.11.09 |
(x-x)는 복식의 경우 항상 양의 0입니까, 때로는 음의 0입니까? (0) | 2020.11.09 |
Docker 컨테이너를 확장하려면 AWS Elastic Beanstalk 또는 Amazon EC2 Container Service (ECS)를 사용해야합니까? (0) | 2020.11.09 |
어휘 분석기 작성의 기초를 어디서 배울 수 있습니까? (0) | 2020.11.09 |