2015-09-03 12 views
12

Te dwa fragmenty kodu robią to samo: dodając dwie tablice float i przechowując wyniki z powrotem w nich.Zwykły kod C++ 10 razy szybszy niż wbudowany asembler. Czemu?

Inline Assembler:

void vecAdd_SSE(float* v1, float* v2) { 
    _asm { 
     mov esi, v1 
     mov edi, v2 
     movups xmm0, [esi] 
     movups xmm1, [edi] 
     addps xmm0, xmm1 
     movups [esi], xmm0 
     movups [edi], xmm0 
    } 
} 

Plain C++ Kod:

void vecAdd_Std(float* v1, float* v2) { 
    v1[0] = v1[0]+ v2[0]; 
    v1[1] = v1[1]+ v2[1]; 
    v1[2] = v1[2]+ v2[2]; 
    v1[3] = v1[3]+ v2[3]; 

    v2[0] = v1[0]; 
    v2[1] = v1[1]; 
    v2[2] = v1[2]; 
    v2[3] = v1[3]; 
} 

Demontaż do kodu C++ (Demontaż wykonany w trybie debugowania, ponieważ nie mogę zobaczyć demontażu w trybie zwolnienia z jakiegoś powodu):

void vecAdd_Std(float* v1, float* v2) { 
push  ebp 
mov   ebp,esp 
sub   esp,0C0h 
push  ebx 
push  esi 
push  edi 
lea   edi,[ebp-0C0h] 
mov   ecx,30h 
mov   eax,0CCCCCCCCh 
rep stos dword ptr es:[edi] 

    v1[0] = v1[0]+ v2[0]; 
mov   eax,4 
imul  ecx,eax,0 
mov   edx,4 
imul  eax,edx,0 
mov   edx,dword ptr [v1] 
mov   esi,dword ptr [v2] 
movss  xmm0,dword ptr [edx+ecx] 
addss  xmm0,dword ptr [esi+eax] 
mov   eax,4 
imul  ecx,eax,0 
mov   edx,dword ptr [v1] 
movss  dword ptr [edx+ecx],xmm0 
    v1[1] = v1[1]+ v2[1]; 
mov   eax,4 
shl   eax,0 
    v1[1] = v1[1]+ v2[1]; 
mov   ecx,4 
shl   ecx,0 
mov   edx,dword ptr [v1] 
mov   esi,dword ptr [v2] 
movss  xmm0,dword ptr [edx+eax] 
addss  xmm0,dword ptr [esi+ecx] 
mov   eax,4 
shl   eax,0 
mov   ecx,dword ptr [v1] 
movss  dword ptr [ecx+eax],xmm0 
    v1[2] = v1[2]+ v2[2]; 
mov   eax,4 
shl   eax,1 
mov   ecx,4 
shl   ecx,1 
mov   edx,dword ptr [v1] 
mov   esi,dword ptr [v2] 
movss  xmm0,dword ptr [edx+eax] 
addss  xmm0,dword ptr [esi+ecx] 
mov   eax,4 
shl   eax,1 
mov   ecx,dword ptr [v1] 
movss  dword ptr [ecx+eax],xmm0 
    v1[3] = v1[3]+ v2[3]; 
mov   eax,4 
imul  ecx,eax,3 
mov   edx,4 
imul  eax,edx,3 
mov   edx,dword ptr [v1] 
mov   esi,dword ptr [v2] 
movss  xmm0,dword ptr [edx+ecx] 
addss  xmm0,dword ptr [esi+eax] 
mov   eax,4 
imul  ecx,eax,3 
mov   edx,dword ptr [v1] 
movss  dword ptr [edx+ecx],xmm0 

    v2[0] = v1[0]; 
mov   eax,4 
imul  ecx,eax,0 
mov   edx,4 
imul  eax,edx,0 
mov   edx,dword ptr [v2] 
mov   esi,dword ptr [v1] 
mov   ecx,dword ptr [esi+ecx] 
mov   dword ptr [edx+eax],ecx 
    v2[1] = v1[1]; 
mov   eax,4 
shl   eax,0 
mov   ecx,4 
shl   ecx,0 
mov   edx,dword ptr [v2] 
mov   esi,dword ptr [v1] 
mov   eax,dword ptr [esi+eax] 
mov   dword ptr [edx+ecx],eax 
    v2[2] = v1[2]; 
mov   eax,4 
shl   eax,1 
mov   ecx,4 
shl   ecx,1 
mov   edx,dword ptr [v2] 
mov   esi,dword ptr [v1] 
mov   eax,dword ptr [esi+eax] 
mov   dword ptr [edx+ecx],eax 
    v2[3] = v1[3]; 
mov   eax,4 
imul  ecx,eax,3 
mov   edx,4 
imul  eax,edx,3 
mov   edx,dword ptr [v2] 
mov   esi,dword ptr [v1] 
mov   ecx,dword ptr [esi+ecx] 
mov   dword ptr [edx+eax],ecx 

} 

Teraz dokonałem pomiaru czasu na tych do funkcji i zauważyłem, że inlin e kod assemblera zajmuje około 10 razy dłużej (w trybie Release). Czy ktoś wie, dlaczego?

+3

Czy możesz pokazać demontaż kodu C++ do porównania? – Erik

+1

Określ także, jakiego kompilatora używasz. (wygląda jak VC++?) –

+0

Użyłem VC++ 2015 – Philinator

Odpowiedz

19

Na moim komputerze (VS2015 trybie 64-bit), inlines kompilatora vecAdd_Std i produkuje

00007FF625921C8F vmovups  xmm1,xmmword ptr [[email protected] (07FF625929D60h)] 
00007FF625921C97 vmovups  xmm4,xmm1 
00007FF625921C9B vcvtss2sd xmm1,xmm1,xmm4 

kod testowy

int main() { 
    float x[4] = {1.0, 2.0, 3.0, 4.0}; 
    float y[4] = {1.0, 2.0, 3.0, 4.0}; 

    vecAdd_Std(x, y); 

    std::cout << x[0]; 
} 
+0

OK, który odpowiada na moje pytanie. A teraz jest jasne, dlaczego nie mogę ustawić punktu przerwania w tej funkcji. Wielkie dzięki za odpowiedź. – Philinator

+1

To oszustwo, dwa razy używasz tego samego wektora float, co usuwa jedno obciążenie pamięci :) –

+4

@Cross_ - To nie miało być "uczciwym punktem odniesienia", ale pokazanie, że demontaż Filinatora nie jest nawet bliski kompilator produkuje w trybie zwolnienia. I ten "ręcznie zoptymalizowany" montaż nie jest automagicznie najlepszym kodem, jaki można uzyskać. –

5

Nie jesteś naprawdę wywołanie funkcji, która wykonuje jeden Instrukcja SSE, jesteś? Nie ma trywialnego narzutu związanego z konfigurowaniem rejestrów xmm, a kopiujesz wartości z pamięci do rejestrów iz powrotem, co zajmie znacznie więcej czasu niż rzeczywiste obliczenia.

Nie zdziwiłbym się, gdyby odkrył, że kompilator włącza wersję C++ tej funkcji, ale nie robi (nie może, naprawdę) zrobić tego samego dla funkcji, które zawierają wbudowany zestaw.

Powiązane problemy