2010-01-11 15 views
19

ho un'applicazione in cui parte del ciclo interno era fondamentalmente:differenza di velocità tra l'utilizzo int e unsigned int miscelato con doppie

double sum = 0; 
for (int i = 0; i != N; ++i, ++data, ++x) sum += *data * x; 

Se x è un int senza segno quindi il codice ha 3 volte più lungo come con int!

Questo faceva parte di un più ampio codice di base, ma ho avuto verso il basso per l'essenziale:

#include <iostream>          
#include <cstdlib>          
#include <vector> 
#include <time.h> 

typedef unsigned char uint8; 

template<typename T> 
double moments(const uint8* data, int N, T wrap) { 
    T pos = 0; 
    double sum = 0.; 
    for (int i = 0; i != N; ++i, ++data) { 
     sum += *data * pos; 
     ++pos; 
     if (pos == wrap) pos = 0; 
    } 
    return sum; 
} 

template<typename T> 
const char* name() { return "unknown"; } 

template<> 
const char* name<int>() { return "int"; } 

template<> 
const char* name<unsigned int>() { return "unsigned int"; } 

const int Nr_Samples = 10 * 1000; 

template<typename T> 
void measure(const std::vector<uint8>& data) { 
    const uint8* dataptr = &data[0]; 
    double moments_results[Nr_Samples]; 
    time_t start, end; 
    time(&start); 
    for (int i = 0; i != Nr_Samples; ++i) { 
     moments_results[i] = moments<T>(dataptr, data.size(), 128); 
    } 
    time(&end); 
    double avg = 0.0; 
    for (int i = 0; i != Nr_Samples; ++i) avg += moments_results[i]; 
    avg /= Nr_Samples; 
    std::cout << "With " << name<T>() << ": " << avg << " in " << (end - start) << "secs" << std::endl; 
} 


int main() { 
    std::vector<uint8> data(128*1024); 
    for (int i = 0; i != data.size(); ++i) data[i] = std::rand(); 
    measure<int>(data); 
    measure<unsigned int>(data); 
    measure<int>(data); 
    return 0; 
} 

compilazione con nessuna ottimizzazione:

[email protected]:/home/luispedro/tmp/so §g++ test.cpp  
[email protected]:/home/luispedro/tmp/so §./a.out 
With int: 1.06353e+09 in 9secs 
With unsigned int: 1.06353e+09 in 14secs 
With int: 1.06353e+09 in 9secs 

Con ottimizzazione:

[email protected]:/home/luispedro/tmp/so §g++ -O3 test.cpp 
[email protected]:/home/luispedro/tmp/so §./a.out 
With int: 1.06353e+09 in 3secs 
With unsigned int: 1.06353e+09 in 12secs 
With int: 1.06353e+09 in 4secs 

Non capisco perché una così grande differenza di velocità. Ho provato a capirlo dall'assemblaggio generato, ma non ho ottenuto nulla. Qualcuno ha qualche pensiero?

È qualcosa a che fare con l'hardware o è una limitazione dei macchinari di ottimizzazione di gcc? Scommetto il secondo.

La mia macchina è un Intel 32 bit con Ubuntu 9.10.

Modifica: Da quando Stephen ha chiesto, ecco la sorgente deselezionata (da una compilazione -O3). Credo che ho ottenuto le principali loop:

versione int:

40: 0f b6 14 0b    movzbl (%ebx,%ecx,1),%edx 
    sum += *data * pos; 
44: 0f b6 d2    movzbl %dl,%edx 
47: 0f af d0    imul %eax,%edx 
     ++pos; 
4a: 83 c0 01    add $0x1,%eax 
     sum += *data * pos; 
4d: 89 95 54 c7 fe ff  mov %edx,-0x138ac(%ebp) 
     ++pos; 
     if (pos == wrap) pos = 0; 
53: 31 d2     xor %edx,%edx 
55: 3d 80 00 00 00   cmp $0x80,%eax 
5a: 0f 94 c2    sete %dl 
    T pos = 0; 
    double sum = 0.; 
    for (int i = 0; i != N; ++i, ++data) { 
5d: 83 c1 01    add $0x1,%ecx 
     sum += *data * pos; 
60: db 85 54 c7 fe ff  fildl -0x138ac(%ebp) 
     ++pos; 
     if (pos == wrap) pos = 0; 
66: 83 ea 01    sub $0x1,%edx 
69: 21 d0     and %edx,%eax 
    T pos = 0; 
    double sum = 0.; 
    for (int i = 0; i != N; ++i, ++data) { 
6b: 39 f1     cmp %esi,%ecx 
     sum += *data * pos; 
6d: de c1     faddp %st,%st(1) 
    T pos = 0; 
    double sum = 0.; 
    for (int i = 0; i != N; ++i, ++data) { 
6f: 75 cf     jne 40 

versione non firmata:

50: 0f b6 34 13    movzbl (%ebx,%edx,1),%esi 
     sum += *data * pos; 
54: 81 e6 ff 00 00 00  and $0xff,%esi 
5a: 31 ff     xor %edi,%edi 
5c: 0f af f0    imul %eax,%esi 
     ++pos; 
5f: 83 c0 01    add $0x1,%eax 
     if (pos == wrap) pos = 0; 
62: 3d 80 00 00 00   cmp $0x80,%eax 
67: 0f 94 c1    sete %cl 
    T pos = 0; 
    double sum = 0.; 
    for (int i = 0; i != N; ++i, ++data) { 
6a: 83 c2 01    add $0x1,%edx 
     sum += *data * pos; 
6d: 89 bd 54 c7 fe ff  mov %edi,-0x138ac(%ebp) 
73: 89 b5 50 c7 fe ff  mov %esi,-0x138b0(%ebp) 
     ++pos; 
     if (pos == wrap) pos = 0; 
79: 89 ce     mov %ecx,%esi 
7b: 81 e6 ff 00 00 00  and $0xff,%esi 
     sum += *data * pos; 
81: df ad 50 c7 fe ff  fildll -0x138b0(%ebp) 
     ++pos; 
     if (pos == wrap) pos = 0; 
87: 83 ee 01    sub $0x1,%esi 
8a: 21 f0     and %esi,%eax 
    for (int i = 0; i != N; ++i, ++data) { 
8c: 3b 95 34 c7 fe ff  cmp -0x138cc(%ebp),%edx 
     sum += *data * pos; 
92: de c1     faddp %st,%st(1) 
    for (int i = 0; i != N; ++i, ++data) { 
94: 75 ba     jne 50 

Questa è la versione -O3, motivo per cui le linee di sorgente saltare su e giù. Grazie.

+0

È piuttosto strano. Quanti test di temporizzazione hai eseguito? In genere, se trovo un multiplo 3x, devo interrogare se ci sono stati processi in background che si verificano contemporaneamente. Hai eseguito 10 test di questo tipo? – wheaties

+0

Per interesse personale mi piacerebbe vedere l'assembly generato da quel codice, ma la mia ipotesi (totalmente non supportata dal fatto, semplicemente un sentimento istintivo) è che Intel probabilmente ottimizza le conversioni in virgola mobile su interi con segno perché i float sono firmati e un la conversione in un numero intero senza segno richiede un ulteriore marshalling dei dati. – Beanz

+0

Se pubblichi lo smontaggio per il ciclo senza segno, scriverò una descrizione dettagliata di dove esattamente i tuoi cicli stanno andando =) –

risposta

33

Ecco perché: molte architetture comuni (incluso x86) hanno un'istruzione hardware per convertire l'int firmato in doubles, ma non hanno una conversione hardware da unsigned a double, quindi il compilatore deve sintetizzare la conversione nel software. Inoltre, l'unico multiplo non firmato su Intel è un multiplo a larghezza intera, mentre i multipli firmati possono utilizzare l'istruzione multiplo firmata.

La conversione del software GCC da int unsigned a double può essere molto subottimale (è quasi certamente, data la grandezza del rallentamento osservato), ma è previsto un comportamento per il codice più veloce quando si utilizzano gli interi con segno.

Assumendo un compilatore intelligente, la differenza dovrebbe essere molto più piccola su un sistema a 64 bit, perché un intero con segno a 64 bit -> doppia conversione può essere utilizzato per eseguire in modo efficiente una conversione senza segno a 32 bit.

Edit: illustrare, questo:

sum += *data * x; 

se le variabili intere sono firmati, dovrebbe compilare in qualcosa in queste righe:

mov  (data), %eax 
imul  %ecx,  %eax 
cvtsi2sd %eax,  %xmm1 
addsd  %xmm1, %xmm0 

d'altra parte, se il numero intero le variabili non sono firmate, non è possibile utilizzare cvtsi2sd per eseguire la conversione, pertanto è necessaria una soluzione software. Mi sarei aspettato di vedere qualcosa di simile:

mov  (data), %eax 
    mul  %ecx   // might be slower than imul 
    cvtsi2sd %eax,  %xmm1 // convert as though signed integer 
    test  %eax,  %eax // check if high bit was set 
    jge  1f    // if it was, we need to adjust the converted 
    addsd  (2^32), %xmm1 // value by adding 2^32 
1: addsd  %xmm1, %xmm0 

Quello sarebbe codegen "accettabile" per la unsigned -> doppia conversione; potrebbe facilmente essere peggio.

Tutto ciò presuppone la generazione di codice in virgola mobile in SSE (credo che questo sia l'impostazione predefinita sugli strumenti di Ubuntu, ma potrei sbagliarmi).

+3

Ecco cosa GCC fa anche in modalità 32 bit: estendendo il 32 bit senza segno a 64 bit firmato e convertirlo con un'istruzione di caricamento a virgola mobile a 64 bit. –

+0

Sembra che non userò unsigned int per codice sensibile alle prestazioni. – JohnJohn

+0

Sebbene Intel chiami il suo "IMUL" 32x32-> 32 di istruzione multiplo, la sua operazione equivale a moltiplicare due "unsigned int" a 32 bit in C (una moltiplicazione con segno in C sarebbe consentita di fare qualsiasi cosa se il prodotto è al di fuori del intervallo di 'int'). Mi aspetto anche che la conversione di un 'unsigned int' a' double' si limiti a promuovere un 'long long'. Il caso complicato sarebbe la conversione di un 'unsigned long long', poiché la conversione come quantità firmata a 64 bit e quindi la regolazione del risultato fornirebbe una semantica di arrotondamento errata su macchine senza un tipo di precisione estesa. – supercat

3

Ecco un codice prodotto da VC++ 6.0 - nessuna ottimizzazione:

4:  int x = 12345; 
0040E6D8 mov   dword ptr [ebp-4],3039h 
5:  double d1 = x; 
0040E6DF fild  dword ptr [ebp-4] 
0040E6E2 fstp  qword ptr [ebp-0Ch] 
6:  unsigned int y = 12345; 
0040E6E5 mov   dword ptr [ebp-10h],3039h 
7:  double d2 = y; 
0040E6EC mov   eax,dword ptr [ebp-10h] 
0040E6EF mov   dword ptr [ebp-20h],eax 
0040E6F2 mov   dword ptr [ebp-1Ch],0 
0040E6F9 fild  qword ptr [ebp-20h] 
0040E6FC fstp  qword ptr [ebp-18h] 

Come si può vedere, la conversione del unsigned fa un po 'più di lavoro.

+1

VC6 ... Wow, dando un calcio alla vecchia scuola. Pensavo che fossi l'unico sul pianeta ad usare ancora quel compilatore. –

+0

Oh no signore, molti di noi sono benedetti con VC++ 6.0. – user7116

1

uscita con Visual Studio 2010 con Intel Q6600 ... (nota: ho aumentato il numero di loop da 128 * 1024-512 * 1024) Modalità

rilascio ...

With int: 4.23944e+009 in 9secs 
With unsigned int: 4.23944e+009 in 18secs 
With int: 4.23944e+009 in 9secs 

modalità di debug ...

With int: 4.23944e+009 in 34secs 
With unsigned int: 4.23944e+009 in 58secs 
With int: 4.23944e+009 in 34secs 

L'ASM nella modalità di rilascio ... (non firmato)

for (int i = 0; i != Nr_Samples; ++i) { 
011714A1 fldz 
011714A3 mov   edx,dword ptr [esi+4] 
011714A6 add   esp,4 
011714A9 xor   edi,edi 
011714AB sub   edx,dword ptr [esi] 
     moments_results[i] = moments<T>(dataptr, data.size(), 128); 
011714AD mov   ecx,dword ptr [ebp-1388Ch] 
011714B3 fld   st(0) 
011714B5 xor   eax,eax 
011714B7 test  edx,edx 
011714B9 je   measure<unsigned int>+79h (11714E9h) 
011714BB mov   esi,edx 
011714BD movzx  ebx,byte ptr [ecx] 
011714C0 imul  ebx,eax 
011714C3 mov   dword ptr [ebp-138A4h],ebx 
011714C9 fild  dword ptr [ebp-138A4h] //only in unsigned 
011714CF test  ebx,ebx //only in unsigned 
011714D1 jns   measure<unsigned int>+69h (11714D9h) //only in unsigned 
011714D3 fadd  qword ptr [[email protected] (11731C8h)] //only in unsigned 
011714D9 inc   eax 
011714DA faddp  st(1),st 
011714DC cmp   eax,80h 
011714E1 jne   measure<unsigned int>+75h (11714E5h) 
011714E3 xor   eax,eax 
011714E5 inc   ecx 
011714E6 dec   esi 
011714E7 jne   measure<unsigned int>+4Dh (11714BDh) 
011714E9 fstp  qword ptr [ebp+edi*8-13888h] 
011714F0 inc   edi 
011714F1 cmp   edi,2710h 
011714F7 jne   measure<unsigned int>+3Dh (11714ADh) 
    } 

L'ASM nella modalità di rilascio ... (firmato)

for (int i = 0; i != Nr_Samples; ++i) { 
012A1351 fldz 
012A1353 mov   edx,dword ptr [esi+4] 
012A1356 add   esp,4 
012A1359 xor   edi,edi 
012A135B sub   edx,dword ptr [esi] 
     moments_results[i] = moments<T>(dataptr, data.size(), 128); 
012A135D mov   ecx,dword ptr [ebp-13890h] 
012A1363 fld   st(0) 
012A1365 xor   eax,eax 
012A1367 test  edx,edx 
012A1369 je   measure<int>+6Fh (12A138Fh) 
012A136B mov   esi,edx 
012A136D movzx  ebx,byte ptr [ecx] 
012A1370 imul  ebx,eax 
012A1373 mov   dword ptr [ebp-1388Ch],ebx 
012A1379 inc   eax 
012A137A fild  dword ptr [ebp-1388Ch] //only in signed 
012A1380 faddp  st(1),st 
012A1382 cmp   eax,80h 
012A1387 jne   measure<int>+6Bh (12A138Bh) 
012A1389 xor   eax,eax 
012A138B inc   ecx 
012A138C dec   esi 
012A138D jne   measure<int>+4Dh (12A136Dh) 
012A138F fstp  qword ptr [ebp+edi*8-13888h] 
012A1396 inc   edi 
012A1397 cmp   edi,2710h 
012A139D jne   measure<int>+3Dh (12A135Dh) 
    } 

interessante ... con modalità di rilascio e SSE abilitato ..... (istruzioni FLD e FLDS rimossi ma 4 istruzioni aggiunte)

With int: 4.23944e+009 in 8secs 
With unsigned int: 4.23944e+009 in 10secs 
With int: 4.23944e+009 in 8secs 


    for (int i = 0; i != Nr_Samples; ++i) { 
00F614C1 mov   edx,dword ptr [esi+4] 
00F614C4 xorps  xmm0,xmm0 //added in sse version 
00F614C7 add   esp,4 
00F614CA xor   edi,edi 
00F614CC sub   edx,dword ptr [esi] 
     moments_results[i] = moments<T>(dataptr, data.size(), 128); 
00F614CE mov   ecx,dword ptr [ebp-13894h] 
00F614D4 xor   eax,eax 
00F614D6 movsd  mmword ptr [ebp-13890h],xmm0 //added in sse version 
00F614DE test  edx,edx 
00F614E0 je   measure<unsigned int>+8Ch (0F6151Ch) 
00F614E2 fld   qword ptr [ebp-13890h] //added in sse version 
00F614E8 mov   esi,edx 
00F614EA movzx  ebx,byte ptr [ecx] 
00F614ED imul  ebx,eax 
00F614F0 mov   dword ptr [ebp-1388Ch],ebx 
00F614F6 fild  dword ptr [ebp-1388Ch] 
00F614FC test  ebx,ebx 
00F614FE jns   measure<unsigned int>+76h (0F61506h) 
00F61500 fadd  qword ptr [[email protected] (0F631C8h)] 
00F61506 inc   eax 
00F61507 faddp  st(1),st 
00F61509 cmp   eax,80h 
00F6150E jne   measure<unsigned int>+82h (0F61512h) 
00F61510 xor   eax,eax 
00F61512 inc   ecx 
00F61513 dec   esi 
00F61514 jne   measure<unsigned int>+5Ah (0F614EAh) 
00F61516 fstp  qword ptr [ebp-13890h] 
00F6151C movsd  xmm1,mmword ptr [ebp-13890h] //added in sse version 
00F61524 movsd  mmword ptr [ebp+edi*8-13888h],xmm1 //added in sse version 
00F6152D inc   edi 
00F6152E cmp   edi,2710h 
00F61534 jne   measure<unsigned int>+3Eh (0F614CEh) 
    } 
0

L'ho eseguito su gcc 4.7.0 su una macchina a 64 bit con Linux. Ho sostituito le chiamate orarie con le chiamate a clock_gettime.

CPU: Intel X5680 @ 3.33 GHZ

bandiere GCC: -Wall -pedantic -O3 -std = C++ 11

Risultati:

With int time per operation in ns: 11996, total time sec: 1.57237 
Avg values: 1.06353e+09 
With unsigned int time per operation in ns: 11539, total time sec: 1.5125 
Avg values: 1.06353e+09 
With int time per operation in ns: 11994, total time sec: 1.57217 
Avg values: 1.06353e+09 

Evidentemente sulla mia macchina/compilatore non firmato è più veloce.

+0

Potrebbe essere che i nuovi gcc sono migliori. Se sei su 64 bit, è, in ogni caso, una macchina diversa. Grazie per le informazioni! – luispedro

Problemi correlati