2013-08-09 4 views
1

알고리즘의 병목 현상 인 수천 개의 루프라는 명령어 블록을 최적화하려고합니다.x86 어셈블리 명령어 최적화

이 코드 블록은 N 벡터 3 (iA 어레이)에 대한 N 행렬 3x3 (iA 어레이)의 곱셈을 계산하고 N 결과를 oV 어레이에 저장합니다.

행렬 및 벡터의 각 행은 SSE 최적화를 사용하기 위해 128 비트 정렬 (4 개 부동 소수점)되어 있습니다 (4 번째 부동 값은 무시 됨).

C++ 코드 :

__m128* ip = (__m128*)iV; 
    __m128* op = (__m128*)oV; 
    __m128* A = (__m128*)iA; 

    __m128 res1, res2, res3; 
    int i; 

    for (i=0; i<N; i++) 
    { 
    res1 = _mm_dp_ps(*A++, *ip, 0x71); 
    res2 = _mm_dp_ps(*A++, *ip, 0x72); 
    res3 = _mm_dp_ps(*A++, *ip++, 0x74); 

    *op++ = _mm_or_ps(res1, _mm_or_ps(res2, res3)); 
    } 

컴파일러는이 지침 생성 낮은 수준의 최적화와

000007FEE7DD4FE0 movaps  xmm2,xmmword ptr [rsi]    //move "ip" in register 
000007FEE7DD4FE3 movaps  xmm1,xmmword ptr [rdi+10h]   //move second line of A in register 
000007FEE7DD4FE7 movaps  xmm0,xmmword ptr [rdi+20h]   //move third line of A in register 
000007FEE7DD4FEB inc   r11d         //i++ 
000007FEE7DD4FEE add   rbp,10h        //op++ 
000007FEE7DD4FF2 add   rsi,10h        //ip++ 
000007FEE7DD4FF6 dpps  xmm0,xmm2,74h      //dot product of 3rd line of A against ip 
000007FEE7DD4FFC dpps  xmm1,xmm2,72h      //dot product of 2nd line of A against ip 
000007FEE7DD5002 orps  xmm0,xmm1       //"merge" of the result of the two dot products 
000007FEE7DD5005 movaps  xmm3,xmmword ptr [rdi]    //move first line of A in register 
000007FEE7DD5008 add   rdi,30h        //A+=3 
000007FEE7DD500C dpps  xmm3,xmm2,71h      //dot product of 1st line of A against ip 
000007FEE7DD5012 orps  xmm0,xmm3       //"merge" of the result 
000007FEE7DD5015 movaps  xmmword ptr [rbp-10h],xmm0   //move result in memory (op) 
000007FEE7DD5019 cmp   r11d,dword ptr [rbx+28h]    //compare i 
000007FEE7DD501D jl   MyFunction+370h (7FEE7DD4FE0h)  //loop 

난 아주 익숙하지를, 그래서 질문은 : 당신은 몇 가지 가능한 최적화를 볼 수 있나요 내가 직접 어셈블리 코드를 작성한다면? 예를 들어

, 내가 변경하는 경우가 더 빠르게 실행됩니다

add   rbp,10h 
movaps  xmmword ptr [rbp-10h],xmm0 

나는 또한 ... 그 ADD 명령이 INC보다 빠른 읽기

+0

당신이 물어 보았던 특별한 마이크로 최적화 (아마도'add' /'movaps'와'add''와'inc'의 순서)는 당신이 코딩하고있는 특정 CPU 종류, 달의 위상, 나는 속도의 차이가 주 계산에 소비 된 시간과 겹칠 것이기 때문에 당신이 시도해도 측정 가능한 차이를 보지 못할 것이라고 상상한다. 현명하게도, 나는 그 코드에 많은 체지방을 보지 못한다. 아마도 SSE 전문가는 벡터화가 인텔의 벡터 지침에 얼마나 잘 맞는지에 대해 의견을 제시 할 수 있습니다. –

+0

* "ADD 명령이 INC보다 빠르다는 것을 읽었습니다 ..."* - 정말요? 나는 조립에 대해선 몰라.하지만 이상하게 보인다. 그렇다면 인수 1을 사용하여 ADD를 사용할 수있을 때 왜 INC를 사용해야합니까? INC가 존재하는 이유는 무엇입니까? –

+0

'add'와'inc'는 플래그와 관련이 있습니다. inc 뒤에 캐리 플래그를 읽는 것이 문제가 될 수 있습니다. P4에서는 다른 방식으로 작동합니다.'inc'는 플래그를 기다려야합니다. 이 경우 플래그를 즉시 덮어 쓰는 것이므로이 'inc'는 P4를 제외한 모든 것에 문제가되지 않습니다. (진지하게, 누가 P4를 신경 쓰는지) – harold

답변

2

rbp-10과 같은 오프셋을 사용하여 간접 주소를 계산하는 것은 "효율적인 주소 계산"단위에서 이러한 종류의 계산을위한 특수 하드웨어가 있기 때문에 매우 저렴합니다. [다른 주소는 생각되지만 생각할 수 없습니다.] 또는 그 이름을 찾기 위해 구글과 어떤 성공을 가지고].

그러나 add rbp,10h[rbp-10h] 사이에는 종속되어있어 문제가 발생할 수 있지만이 경우에는 의문의 여지가 있습니다. 귀하의 경우에는 rbp-10 사이의 장거리가있어 문제가되지 않습니다. 컴파일러는 아마도 그 시점에서 "자유"이기 때문에 아마도 멀리까지 퍼팅 할 것입니다. 프로세서가 데이터가 외부에서 이전에 읽은 xmm 레지스터로 들어올 때까지 기다릴 것이기 때문입니다.즉 "우리는 사이에 충실 할 수있는 모든 작업은 루프의 시작 부분에 xmm0, xmm1xmm2의 읽고, 프로세서가 데이터 기다리고있을 것입니다 때문에 xmm0, xmm1xmm2를 사용하여 dpps 지침이 도움이 될 것입니다 도달하기 전에 "dpps 결과를 계산할 수 있습니다.

2

나 '한

movaps  xmmword ptr [rbp],xmm0 
add   rbp,10h 

에 의해 많은 x86 어셈블리 최적화를 수행 했으므로 훌륭한 학습 경험이라고 말할 수 있습니다. 컴파일러가 어떻게 작동하는지에 대해 많은 것을 가르쳐 주었고 가장 큰 것은 컴파일러가 일반적으로 컴파일러가하는 일에 꽤 능숙하다는 것입니다. 나는 그 말이 경박 한 의견이지만, 사실입니다 ...

여러분이 내린 최적화로 인해 한 프로세서 제품군에서는 긍정적 인 결과를, 다른 프로세서 제품군에서는 부정적인 결과를 얻을 수 있다는 것도 알게되었습니다. 파이프 라이닝, 분기 예측 및 프로세서 캐시와 같은 것들이 중요한 역할을합니다 ... 매우 특정한 하드웨어 구성을 목표로하지 않는 한 개선 사항에 대한 가정에주의하십시오.

rbp-10h 오프셋을 제거하기 위해 추가 순서를 변경하는 방법에 대한 구체적인 질문은 ... 명백한 개선으로 보입니다. 설명서를 읽음으로써 확인해야하지만, -10h 메모리 오프셋이옵니다. 그 명령에서 무료로. 그리고 add을 움직이면 파이프 라인 된 명령이 중단되어 실제로 클록 사이클 손실이 발생할 수 있습니다. 실험 해봐야 겠어.

+0

예 현대의 x86은 세밀한 최적화를위한 기질이 좋은 짐승이다. 개별 명령어의 바이트 정렬조차 프로세서에 따라 차이를 만들 수 있습니다. –

1

위 코드를 개선하기 위해 수행 할 수있는 몇 가지 작업이 있습니다. 일반적으로 변경된 값을 사용하면 프로세서 대기가 발생하여 결과를 기다립니다. 따라서이 라인은 벌금을 부과합니다 : -

add   rbp,10h 
movaps  xmmword ptr [rbp-10h],xmm0 

하지만 꽤 멀리 떨어져 두 라인 위의 코드에서

, 즉 정말 문제가되지 않습니다 그래서. 다른 사람들이 이미 말했듯이, rbp-10h은 주소 계산 하드웨어가 처리한다는 점에서 '무료'입니다.

movaps xmm3,xmmword ptr [rdi]을 한 줄 위로 이동하고 두 줄의 다른 줄을 다시 정렬 할 수 있습니다.

그만 가치가 있습니까?

NO

알고리즘은 시간이 RAM에서 데이터를 읽는 데 걸리는 것을 의미

<blink> memory bandwidth limited </blink>* 

때문에이 어떤에서 실제 성능 이득을 볼 운이 좋은 것 CPU에 들어가는 시간은 CPU가 처리하는 데 걸리는 시간보다 큽니다. 최악의 경우, 메모리 주소를 읽는 것은 페이지 오류와 디스크 읽기를 포함 할 수 있습니다. prefetch 명령어는 도움이되지 않습니다. 스트리밍 SIMD 확장은 CPU로 데이터를 스트리밍하기 위해 최적화되었으므로 (메모리 인터페이스는 4 개의 개별 스트림 IIRC를 처리 할 수 ​​있습니다).

작은 데이터 집합 (아마도 FFT)에서 많은 계산을 수행한다면 어셈블러를 손으로 제작하여 많은 것을 얻을 수 있습니다. 하지만 알고리즘이 매우 간단하여 CPU가 데이터 도착을 기다리는 데 많은 시간을 허비하고 있습니다. RAM의 속도를 아는 경우 알고리즘의 최대 처리량을 계산하여 현재 처리량과 비교할 수 있습니다 (최대 이론 처리량에 도달하지는 못할 것입니다).

메모리 스톨을 최소화하기 위해 할 수있는 일이 있으며, 개별 지침을 따르는 것이 아니라 더 높은 수준의 변경입니다 (종종 알고리즘 최적화가 더 나은 결과를 얻습니다). 가장 간단한 방법은 입력 데이터를 이중 버퍼링하는 것입니다.

load set 1 
mainloop: 
    load set 2 
    do processing on set 1 
    save set 1 result 
    load set 1 
    do processing on set 2 
    save set 2 result 
    goto mainloop 

희망이 당신에게 몇 가지 아이디어를 주었어요 - (당신은 단지 SIMD 레지스터의 네 가지를 사용하고 여기에 할 수) 두 그룹으로 설정 레지스터를 나눈다. 훨씬 더 빨리 진행되지는 않지만 여전히 흥미로운 운동이며 많은 것을 배울 수 있습니다.

  • RIP 깜박임.