2017-10-01 18 views
7

벡터의 L1 표준 벡터를 많이 사용하는 스크립트를 최적화해야합니다. 이 경우 L1 norm은 절대 값의 합계 일뿐입니다. 이 작업에서 numpy가 얼마나 빨리 수행되는지를 알 때 나는 이상한 것을 발견했습니다 : 모든 벡터 요소를 추가하는 것은 벡터의 모든 요소의 절대 값을 취하는 것보다 약 3 배 빠릅니다. 이것은 놀라운 결과입니다. 덧셈은 절대 값을 취하는 것에 비해 꽤 복잡합니다. 단지 데이터 블록의 32 번째 비트 (float32라고 가정) 만 제로화해야합니다.numpy.absolute()가 너무 느린 이유는 무엇입니까?

왜 추가가 간단한 비트 연산보다 3 배 빠릅니까?

import numpy as np 

a = np.random.rand(10000000) 

%timeit np.sum(a) 
13.9 ms ± 87.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 

%timeit np.abs(a) 
41.2 ms ± 92.3 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 
+0

분기 예측으로 인해 발생 가능성이 높음 - https://stackoverflow.com/questions/11227809/why-is-it-faster-to-process-a-sorted-array-than-an-unsorted-array – Maxim

+0

' 내가 입력 배열을 정렬 할 때 어떤 속도 향상을 보여줍니다. – Lugi

답변

2

np.sum은 스칼라를 반환합니다. np.abs 동일한 크기의 새 배열을 반환합니다. 새로운 배열에 대한 메모리 할당은 여기에서 가장 많은 시간을 차지합니다.

>>> timeit("np.abs(a)", "import numpy as np; a = np.random.rand(10000000)", number=100) 
3.565487278989167 
>>> timeit("np.abs(a, out=a)", "import numpy as np; a = np.random.rand(10000000)", number=100) 
0.9392949139873963 

out=a이 NumPy와가 이전 데이터를 덮어 쓰기, 같은 배열 a에 결과를 넣어 알 인수를 비교. 따라서 속도 향상.

>>> timeit("np.sum(a)", "import numpy as np; a = np.random.rand(10000000)", number=100) 
0.6874654769926565 

하지만 많은 쓰기 메모리 액세스를 필요로하지 않습니다

합계는 약간 빠른 아직도있다.

a를 덮어 쓰지 않으려면 같은 유형 및 크기의 배열 abs을 반복적으로 가져와야한다고 가정하고 abs의 출력을 위해 다른 배열을 제공하는 것이 좋습니다. 참고로 np.linalg(a, 1)

, np.linalg의 절반 시간

b = np.empty_like(a) # done once, outside the loop 
np.abs(a, out=b) 
np.sum(b) 

실행 abs(x) 새로운 어레이에 대한 메모리 할당을 포함

add.reduce(abs(x), axis=axis, keepdims=keepdims) 

같은 L1 놈을 계산한다. 이상적


은 RAM 모든 출력을 이동 한 후 합계를 검색하지 않고 모두 절대 값 (또는 다른 "ufunc」의 결과)의 (최대 또는 최소 또는) 합을 계산하는 방법이 될/최대/최소 NumPy 레포에 대한 논의가있었습니다. 가장 최근에는 add a max_abs ufunc 이었지만 구현에 도달하지 못했습니다.

ufunc.reduce 방법 add 또는 logaddexp 같은 두 개의 입력을 갖는 함수를 사용할 수 있지만로 감소 할 addabs 함수 (x, y : x+abs(y))는 없다.

+0

당신이 제공 한 기능이 nx.linalg l1 표준의 2 배에 달하는 것이 여전히 불필요한 작업을 많이한다고 믿습니다. np.abs (a, out = b) 결과를 RAM으로 옮긴 다음 np.sum (b)이 결과를 RAM에서 다시 읽습니다. 내 생각은 맞습니까? – Lugi

+0

수정. 이상적으로는 "절대 값 추가"에 대해 ufunc'addabs'를 사용하고 [ufunc.reduce (a)]를 실행하십시오 (https://docs.scipy.org/doc/numpy-1.13.0/reference/). generated/numpy.ufunc.reduce.html), 또는 단순히 중간 배열을 생성하지 않는'abssum' 배열 메소드를 가질 수 있습니다. 이것은 몇 년 전에 간략하게 [NumPy issue tracker] (https://github.com/numpy/numpy/issues/5218)에서 논의되었습니다. – FTP

3

여기 몇 가지 사항을 고려해야합니다. sum은 스칼라를 반환합니다. abs은 배열을 반환합니다. 따라서 두 개의 숫자를 더하고 절대 값을 취하는 경우에도 배열을 생성해야하기 때문에 동일한 속도 인 abs이 더 느립니다. 그리고 두 배의 원소를 처리해야합니다 (input + writing에서 output으로 읽음).

그래서 이러한 속도에서 비트 연산과 비교하여 속도를 추론 할 수는 없습니다.

는 절대

%timeit np.full_like(a, 1); np.sum(a) 
13.4 ms ± 358 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 
%timeit abs(a) 
9.64 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 
을 복용 대 합 + 메모리 할당을 각각의 값

%timeit a + 0.1 
9 ms ± 155 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 
%timeit abs(a) 
9.98 ms ± 532 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 

의 절대 복용 대 배열의 각 값에 뭔가를 추가하거나 비교하는 더 빠른 경우가 있지만 확인할 수 있습니다

계산 속도를 향상 시키려면 numba (또는 Cython 또는 C 또는 Fortran 루틴을 직접 작성)를 시도하여 메모리 할당을 피하십시오.

import numba as nb 

@nb.njit 
def sum_of_abs(arr): 
    sum_ = 0 
    for item in arr: 
     sum_ += abs(item) 
    return sum_ 

sum_of_abs(a) # one call for the jitter to kick in 
%timeit sum_of_abs(a) 
# 2.44 ms ± 315 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 
+0

(실제로 입력 배열을 10 배 더 크게 만들었으므로 여기서 타이밍은 OP와 비교하여 10 배 더 길다). 합계 및 메모리 할당의 경우 : 142 ms ± 1.97 ms 절대 값 : 428 ms ± 20.4 ms 일반 메모리 할당 : % timeit np.empty_like (a) 6.44 μs ± 22.8 ns이므로 메모리 할당에는 거의 시간이 걸리지 않는 것으로 보입니다. – Lugi

+0

@ 루기 나는'np.empty_like'라는 대답을 업데이트했다. 실제로 OS에 따라 배열을 할당 할 수있다. 이제는 np.full_like를 사용했는데, 이는 두 배의 메모리 처리량을 "에뮬레이션"하기 때문입니다. 그러나 비교는 "abs"대 "addition"의 속도를 실제로 허용한다는 의미에서 여전히 "공정"하지 않습니다. – MSeifert