[arrays] arrayfun은 matlab의 명시 적 루프보다 훨씬 느릴 수 있습니다. 왜?

다음에 대해 다음과 같은 간단한 속도 테스트를 고려하십시오 arrayfun.

T = 4000;
N = 500;
x = randn(T, N);
Func1 = @(a) (3*a^2 + 2*a - 1);

tic
Soln1 = ones(T, N);
for t = 1:T
    for n = 1:N
        Soln1(t, n) = Func1(x(t, n));
    end
end
toc

tic
Soln2 = arrayfun(Func1, x);
toc

내 컴퓨터 (Linux Mint 12의 Matlab 2011b)에서이 테스트의 출력은 다음과 같습니다.

Elapsed time is 1.020689 seconds.
Elapsed time is 9.248388 seconds.

뭐야?!? arrayfun, 분명히 깨끗해 보이는 솔루션이지만 훨씬 느립니다. 여기서 무슨 일이 일어나고 있습니까?

또한 비슷한 스타일의 테스트를 수행 한 cellfun결과 명시 적 루프보다 약 3 배 느린 것으로 나타났습니다. 다시 말하지만이 결과는 내가 예상했던 것과 반대입니다.

내 질문은 :arrayfun그리고 cellfun너무 느린? 그리고이 점을 감안할 때 코드를보기 좋게 만드는 것 외에 사용하는 좋은 이유가 있습니까?

참고 :arrayfun 여기서는 병렬 처리 도구 상자의 GPU 버전이 아닌 표준 버전에 대해 이야기하고 있습니다 .

편집 : 분명히하기 위해 Func1Oli가 지적한대로 위의 벡터화가 가능 하다는 것을 알고 있습니다. 실제 질문을 위해 간단한 속도 테스트를 제공하기 때문에 선택했습니다.

편집 : grungetta의 제안에 따라 feature accel off. 결과는 다음과 같습니다.

Elapsed time is 28.183422 seconds.
Elapsed time is 23.525251 seconds.

즉, 차이점의 큰 부분은 JIT 가속기가 명시 적 for루프 속도를 높이는 것보다 훨씬 더 나은 작업을 수행한다는 것 arrayfun입니다. 이것은 arrayfun실제로 더 많은 정보를 제공하기 때문에 나에게는 이상해 보인다 . 즉, 그 사용은 호출 순서가 Func1중요하지 않다는 것을 보여주기 때문이다. 또한 JIT 가속기가 켜져 있든 꺼져 있든 내 시스템은 하나의 CPU 만 사용합니다.



답변

다른 버전의 코드를 실행하여 아이디어를 얻을 수 있습니다. 루프에서 함수를 사용하는 대신 명시 적으로 계산을 작성하는 것이 좋습니다.

tic
Soln3 = ones(T, N);
for t = 1:T
    for n = 1:N
        Soln3(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
    end
end
toc

내 컴퓨터에서 계산하는 시간 :

Soln1  1.158446 seconds.
Soln2  10.392475 seconds.
Soln3  0.239023 seconds.
Oli    0.010672 seconds.

이제 완전히 ‘벡터화 된’솔루션이 분명히 가장 빠르지 만 모든 x 항목에 대해 호출 할 함수를 정의하는 것은 엄청난 오버 헤드 임을 알 수 있습니다 . 계산을 명시 적으로 작성하는 것만으로도 요소 5의 속도가 빨라졌습니다. 이것은 MATLABs JIT 컴파일러 가 인라인 함수를 지원하지 않는다는 것을 보여줍니다 . 거기 gnovice의 대답에 따르면 실제로 익명의 함수보다 정상적인 함수를 작성하는 것이 좋습니다. 시도 해봐.

다음 단계-내부 루프 제거 (벡터화) :

tic
Soln4 = ones(T, N);
for t = 1:T
    Soln4(t, :) = 3*x(t, :).^2 + 2*x(t, :) - 1;
end
toc

Soln4  0.053926 seconds.

또 다른 요소 5 속도 향상 :이 문장에 MATLAB에서 루프를 피해야한다는 내용이 있습니다 … 아니면 정말 있습니까? 그럼 이것 좀 봐

tic
Soln5 = ones(T, N);
for n = 1:N
    Soln5(:, n) = 3*x(:, n).^2 + 2*x(:, n) - 1;
end
toc

Soln5   0.013875 seconds.

‘완전한’벡터화 된 버전에 훨씬 더 가깝습니다. Matlab은 행렬을 열 단위로 저장합니다. 가능한 경우 항상 계산을 ‘열 단위’로 벡터화하도록 구조화해야합니다.

이제 Soln3로 돌아갈 수 있습니다. 루프 순서는 ‘행 방향’입니다. 그것을 바꿀 수 있습니다

tic
Soln6 = ones(T, N);
for n = 1:N
    for t = 1:T
        Soln6(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
    end
end
toc

Soln6  0.201661 seconds.

더 좋지만 여전히 매우 나쁩니다. 단일 루프-좋습니다. 이중 루프-나쁨. MATLAB이 루프의 성능을 개선하기 위해 적절한 작업을 수행했다고 생각하지만 여전히 루프 오버 헤드가 있습니다. 내부에 더 무거운 작업이 있다면 눈치 채지 못할 것입니다. 그러나이 계산은 메모리 대역폭 제한이 있으므로 루프 오버 헤드를 볼 수 있습니다. 그리고 당신은 보다 더 명확하게이 FUNC1를 호출의 오버 헤드를 참조하십시오.

그래서 arrayfun은 무엇입니까? 거기에도 기능이 포함되어 있지 않으므로 많은 오버 헤드가 발생합니다. 그러나 이중 중첩 루프보다 훨씬 더 나쁜 이유는 무엇입니까? 실제로 cellfun / arrayfun 사용에 대한 주제는 여러 번 광범위하게 논의되었습니다 (예 : here , here , herehere ). 이러한 함수는 단순히 느리기 때문에 이러한 세분화 계산에 사용할 수 없습니다. 코드 간결함과 셀과 배열 간의 멋진 변환을 위해 사용할 수 있습니다. 그러나 함수는 작성한 것보다 무거워 야합니다.

tic
Soln7 = arrayfun(@(a)(3*x(:,a).^2 + 2*x(:,a) - 1), 1:N, 'UniformOutput', false);
toc

Soln7  0.016786 seconds.

Soln7은 이제 셀입니다. 때로는 유용합니다. 이제 코드 성능이 상당히 좋으며 출력으로 셀이 필요한 경우 완전히 벡터화 된 솔루션을 사용한 후 행렬을 변환 할 필요가 없습니다.

그렇다면 arrayfun이 단순한 루프 구조보다 느린 이유는 무엇입니까? 안타깝게도 사용 가능한 소스 코드가 없기 때문에 확실히 말할 수 없습니다. arrayfun은 모든 종류의 다양한 데이터 구조와 인수를 처리하는 범용 함수이기 때문에 루프 중첩으로 직접 표현할 수있는 간단한 경우에는 반드시 매우 빠르지는 않습니다. 오버 헤드가 어디서 오는지 알 수 없습니다. 더 나은 구현으로 오버 헤드를 피할 수 있습니까? 아마. 그러나 불행히도 우리가 할 수있는 유일한 일은 성능을 연구하여 잘 작동하는 경우와 그렇지 않은 경우를 식별하는 것입니다.

업데이트이 테스트의 실행 시간이 짧기 때문에 신뢰할 수있는 결과를 얻기 위해 테스트 주변에 루프를 추가했습니다.

for i=1:1000
   % compute
end

아래에 주어진 시간 :

Soln5   8.192912 seconds.
Soln7  13.419675 seconds.
Oli     8.089113 seconds.

arrayfun은 여전히 ​​나쁘지만 벡터화 된 솔루션보다 적어도 3 배 더 나쁘지는 않습니다. 반면에 열 단위 계산을 사용하는 단일 루프는 완전히 벡터화 된 버전만큼 빠릅니다.이 모든 작업은 단일 CPU에서 수행되었습니다. Soln5 및 Soln7의 결과는 2 코어로 전환해도 변경되지 않습니다. Soln5에서는 parfor를 사용하여 병렬화해야합니다. 속도 향상은 잊어 버리세요 … Soln7은 arrayfun이 병렬로 실행되지 않기 때문에 병렬로 실행되지 않습니다. 반면 Olis 벡터화 버전 :

Oli  5.508085 seconds.


답변

왜냐하면 !!!!

x = randn(T, N); 

gpuarray유형 이 아닙니다 .

당신이해야 할 일은

x = randn(T, N,'gpuArray');


답변