TL; DR: Perché la moltiplicazione/fusione dei dati in size_t
è lenta e perché varia in base alla piattaforma?Prestazioni di trasmissione da size_t a double
Sto riscontrando alcuni problemi di prestazioni che non comprendo completamente. Il contesto è un frame grabber della telecamera in cui un'immagine Uint16_t 128x128 viene letta e post-elaborata ad una velocità di diversi 100 Hz.
Nella postelaborazione viene generato un istogramma frame->histo
che è di uint32_t
e dispone di thismaxval
= 2^16 elementi, in pratica vengono calcolati tutti i valori di intensità. Utilizzando questo istogramma calcolo la somma e somma squadrato
double sum=0, sumsquared=0;
size_t thismaxval = 1 << 16;
for(size_t i = 0; i < thismaxval; i++) {
sum += (double)i * frame->histo[i];
sumsquared += (double)(i * i) * frame->histo[i];
}
profilatura il codice con profilo Mi sono le seguenti (campioni, percentuale, codice):
58228 32.1263 : sum += (double)i * frame->histo[i];
116760 64.4204 : sumsquared += (double)(i * i) * frame->histo[i];
o, la prima linea riprende 32 % del tempo della CPU, la seconda riga del 64%.
Ho fatto un po 'di benchmark e sembra che il tipo di dati/casting sia problematico. Quando cambio il codice in
uint_fast64_t isum=0, isumsquared=0;
for(uint_fast32_t i = 0; i < thismaxval; i++) {
isum += i * frame->histo[i];
isumsquared += (i * i) * frame->histo[i];
}
esegue ~ 10 volte più veloce. Tuttavia, questo successo in termini di prestazioni varia anche a seconda della piattaforma. Sulla workstation, una CPU Core i7 950 @ 3,07 GHz, il codice è 10 volte più veloce. Sul mio Macbook8,1, che ha un processore Intel Core i7 Sandy Bridge da 2.7 GHz (2620M), il codice è solo 2x più veloce.
Ora mi chiedo:
- Perché il codice originale in modo lento e facilmente accelerato?
- Perché varia così tanto per piattaforma?
Aggiornamento:
ho compilato il codice precedente con
g++ -O3 -Wall cast_test.cc -o cast_test
Update2:
Ho eseguito il codice ottimizzato attraverso un profiler (Instruments su Mac, come Shark) e ha trovato due cose:
1) Il ciclo stesso richiede in alcuni casi una notevole quantità di tempo. thismaxval
è di tipo size_t
.
for(size_t i = 0; i < thismaxval; i++)
prende il 17% del mio tempo di esecuzione totalefor(uint_fast32_t i = 0; i < thismaxval; i++)
prende 3,5%for(int i = 0; i < thismaxval; i++)
non compare nel profiler, presumo che sia inferiore a 0.1%
2) I tipi di dati e la materia colata come segue:
sumsquared += (double)(i * i) * histo[i];
15% (consize_t i
)sumsquared += (double)(i * i) * histo[i];
36% (conuint_fast32_t i
)isumsquared += (i * i) * histo[i];
13% (conuint_fast32_t i
,uint_fast64_t isumsquared
)isumsquared += (i * i) * histo[i];
11% (conint i
,uint_fast64_t isumsquared
)
Sorprendentemente, int
è più veloce di uint_fast32_t
?
Update4:
ho incontrato qualche altro test con diversi tipi di dati e compilatori diversi, su una macchina. I risultati sono i seguenti.
Per testd 0 - 2, il codice relativo è
for(loop_t i = 0; i < thismaxval; i++)
sumsquared += (double)(i * i) * histo[i];
con sumsquared
un doppio, e loop_t
size_t
, uint_fast32_t
e int
per test 0, 1 e 2.
Per i test 3-- 5 il codice è
for(loop_t i = 0; i < thismaxval; i++)
isumsquared += (i * i) * histo[i];
con isumsquared
di tipo uint_fast64_t
e loop_t
nuovo size_t
, uint_fast32_t
e int
per test 3, 4 e 5.
I compilatori ho usato sono 4.2.1 gcc, gcc 4.4.7, gcc 4.6.3 e 4.7.0 gcc. I tempi sono in percentuale del tempo totale della CPU del codice, quindi mostrano le prestazioni relative, non assolute (sebbene il tempo di esecuzione fosse abbastanza costante a 21 secondi). Il tempo di CPU è per entrambe le due linee, perché non sono sicuro che il profiler abbia correttamente separato le due linee di codice.
gcc: 4.2.1 4.4.7 4.6.3 4.7.0 ---------------------------------- test 0: 21.85 25.15 22.05 21.85 test 1: 21.9 25.05 22 22 test 2: 26.35 25.1 21.95 19.2 test 3: 7.15 8.35 18.55 19.95 test 4: 11.1 8.45 7.35 7.1 test 5: 7.1 7.8 6.9 7.05
o:
Sulla base di questo, sembra che casting è costoso, indipendentemente dal tipo integer che uso.
Inoltre, sembra che gcc 4.6 e 4.7 non siano in grado di ottimizzare correttamente il ciclo 3 (size_t e uint_fast64_t).
potresti provare anche con 'uint_fast32_t'? Un'ipotesi è che è più veloce a causa del fatto che il secondo tipo di dati ha la stessa lunghezza d'onda delle istruzioni della macchina (64-bit). Supponendo di avere almeno una macchina a 64 bit. Mi aspetto che il fast32 sia anche più lento. [modifica] potresti testare anche la dimensione di entrambi 'uint_fast32_t' e' uint_fast64_t'? La mia ipotesi è che il 32 sia effettivamente a 64 bit. – Yuri
Intendi 'uint_fast32_t isum'? Potrei provare, anche se penso che potrebbe traboccare, ed è per questo che ho usato uint_fast64_t. – Tim
Bene, per 1 .: Reason in qualche modo impone che il casting di float e le operazioni float debbano essere più lente delle operazioni int direttamente (anche se int-to-float non dovrebbe essere così malvagio come float-to-int), ancora di più quindi con lo stack x87 ottimale. Lo compili con il supporto SSE? –