2015-09-03 13 views
12

Questi due frammenti di codice fanno la stessa cosa: aggiungendo due array mobili e memorizzando il risultato in essi.Codice C++ semplice 10 volte più veloce dell'assemblatore incorporato. Perché?

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 codice C++:

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]; 
} 

Smontaggio per codice C++ (smontaggio effettuato in modalità debug, perché non riesco a visualizzare il disassemblaggio in modalità di rilascio per qualche motivo):

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 

} 

Ora ho effettuato una misurazione del tempo su quelle per le funzioni e ho notato che l'inlin Il codice dell'assembler richiede circa 10 volte di più (in modalità di rilascio). Qualcuno sa perché?

+3

Puoi mostrare lo smontaggio del codice C++ per il confronto? – Erik

+1

Specifica anche quale compilatore stai utilizzando. (sembra VC++?) –

+0

Ho usato VC++ 2015 – Philinator

risposta

19

Sulla mia macchina (VS2015 modalità a 64 bit), gli inline del compilatore vecAdd_Std e produce

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

Codice di prova

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, questo risponde alla mia domanda. E ora è chiaro il motivo per cui non posso impostare un breakpoint in questa funzione. Grazie mille per la tua risposta. – Philinator

+1

Questo è un imbroglio, stai usando lo stesso vettore float due volte, rimuovendo un carico di memoria :) –

+4

@Cross_ - Questo non voleva essere un "punto di riferimento giusto", ma mostrare che lo smontaggio di Philinator non è nemmeno vicino a quello il compilatore produce in modalità di rilascio. E quell'assemblaggio "ottimizzato a mano" non è automagicamente il miglior codice che si possa ottenere. –

5

Non sta chiamando in realtà una funzione che esegue uno Istruzione SSE, vero? C'è un overhead non banale coinvolto nella configurazione dei registri xmm, e stai copiando i valori dalla memoria ai registri e viceversa, il che richiederà molto più tempo del calcolo effettivo.

Non sarei affatto sorpreso di scoprire che il compilatore inline la versione C++ della funzione, ma non (non può, davvero) fare lo stesso per le funzioni che contengono l'assembly inline.

Problemi correlati