있다 numexpr , numba 와 사이 썬은 주변에,이 답변의 목표는 고려 이러한 가능성을하는 것입니다.
그러나 먼저 명백한 것을 말합시다. 파이썬 함수를 numpy-array에 어떻게 매핑하든 관계없이 모든 평가에 대해 파이썬 함수를 유지합니다.
- numpy-array 요소는 Python 객체로 변환되어야합니다 (예 :
Float
) .
- 모든 계산은 파이썬 객체를 사용하여 수행되므로 인터프리터, 동적 디스패치 및 불변 객체의 오버 헤드가 발생합니다.
따라서 실제로 배열을 반복하는 데 사용되는 기계는 위에서 언급 한 오버 헤드로 인해 큰 역할을하지 않습니다-numpy의 내장 기능을 사용하는 것보다 훨씬 느립니다.
다음 예제를 보자.
# numpy-functionality
def f(x):
return x+2*x*x+4*x*x*x
# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"
np.vectorize
순수한 파이썬 함수 클래스의 접근 방식을 대표하여 선정되었습니다. perfplot
(이 답변의 부록에있는 코드 참조)을 사용 하면 다음과 같은 실행 시간이 발생합니다.
우리는 numpy 접근 방식이 순수 파이썬 버전보다 10x-100x 빠릅니다. 더 큰 어레이 크기의 성능 저하는 데이터가 더 이상 캐시에 맞지 않기 때문일 수 있습니다.
또한 vectorize
많은 메모리를 사용하므로 메모리 사용량이 종종 병목 현상 이라고 언급 할 가치가 있습니다. SO 질문 ). 또한 numpy의 문서는np.vectorize
는 "성능이 아닌 편의상 주로 제공된다"고 명시하고 있습니다.
처음부터 C-extension을 작성하는 것 외에 성능이 필요한 경우 다른 도구를 사용해야합니다.
numpy-performance는 후드 아래 순수한 C이기 때문에 numpy-performance가 얻는 것만 큼 자주 듣는다고 들었습니다. 그러나 개선의 여지가 많이 있습니다!
벡터화 된 numpy 버전은 많은 추가 메모리와 메모리 액세스를 사용합니다. Numexp 라이브러리는 numpy 배열을 바둑판 식으로 정리하여 더 나은 캐시 활용도를 얻습니다.
# less cache misses than numpy-functionality
import numexpr as ne
def ne_f(x):
return ne.evaluate("x+2*x*x+4*x*x*x")
다음과 같은 비교로 이어집니다.
위의 줄거리에서 모든 것을 설명 할 수는 없습니다. 처음에는 numexpr-library의 오버 헤드가 더 커질 수 있지만 캐시를 더 잘 활용하기 때문에 더 큰 배열의 경우 약 10 배 빠릅니다!
또 다른 접근법은 함수를 jit 컴파일하여 실제 pure-C UFunc를 얻는 것입니다. 이것은 numba의 접근 방식입니다.
# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
return x+2*x*x+4*x*x*x
원래 numpy 접근 방식보다 10 배 빠릅니다.
그러나 작업은 당황스럽게 병렬화 가능하므로 prange
루프를 병렬로 계산하는 데 사용할 수도 있습니다 .
@nb.njit(parallel=True)
def nb_par_jitf(x):
y=np.empty(x.shape)
for i in nb.prange(len(x)):
y[i]=x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y
예상대로 병렬 기능은 입력이 작을수록 느리지 만 크기가 클수록 (거의 2 배) 빠릅니다.
numba는 numpy-array를 사용한 작업 최적화를 전문으로하는 반면 Cython은보다 일반적인 도구입니다. numba와 동일한 성능을 추출하는 것이 더 복잡합니다. 종종 llvm (numba) 대 로컬 컴파일러 (gcc / MSVC)로 다운됩니다.
%%cython -c=/openmp -a
import numpy as np
import cython
#single core:
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_f(double[::1] x):
y_out=np.empty(len(x))
cdef Py_ssize_t i
cdef double[::1] y=y_out
for i in range(len(x)):
y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y_out
#parallel:
from cython.parallel import prange
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_par_f(double[::1] x):
y_out=np.empty(len(x))
cdef double[::1] y=y_out
cdef Py_ssize_t i
cdef Py_ssize_t n = len(x)
for i in prange(n, nogil=True):
y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y_out
Cython은 다소 느린 기능을 수행합니다.
결론
분명히 하나의 기능에 대해서만 테스트해도 아무런 효과가 없습니다. 또한 선택한 함수 예제의 경우 메모리의 대역폭이 10 ^ 5 요소보다 큰 크기의 병목 현상이라는 점을 명심해야합니다. 따라서이 지역의 numba, numexpr 및 cython에 대해 동일한 성능을 보였습니다.
결국, 궁극의 대답은 함수의 유형, 하드웨어, 파이썬 배포 및 기타 요인에 따라 다릅니다. 예를 아나콘다 분포를 들어 NumPy와의 기능에 대한 인텔의 VML을 사용하여 numba 능가하는 성능 (이 SVML를 사용하지 않는 한,이 참조 SO-게시물을 초월 기능이 좋아 쉽게 용) exp
, sin
, cos
및 유사 - 참조 예를 들어 다음과 같은 SO-포스트 .
그러나이 조사와 지금까지의 경험을 통해 numba는 초월적인 기능이없는 한 최고의 성능을 가진 가장 쉬운 도구 인 것 같습니다.
perfplot -package를 사용 하여 실행 시간 플로팅 :
import perfplot
perfplot.show(
setup=lambda n: np.random.rand(n),
n_range=[2**k for k in range(0,24)],
kernels=[
f,
vf,
ne_f,
nb_vf, nb_par_jitf,
cy_f, cy_par_f,
],
logx=True,
logy=True,
xlabel='len(x)'
)