TL;DR: Pourquoi multiplier/moulage de données en size_t
lent et pourquoi cela varie selon la plate-forme?
Je vais avoir quelques problèmes de performance que je ne comprends pas tout. Le contexte est un appareil d'acquisition d'images où un 128x128 uint16_t image est de lire et de post-traitement à un taux de plusieurs de 100 Hz.
Dans le post-traitement-je générer un histogramme frame->histo
qui est de uint32_t
et a thismaxval
= 2^16 éléments, fondamentalement, je comptabiliser toutes les valeurs d'intensité. À l'aide de cet histogramme-je calculer la somme et le carré de la somme:
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];
}
Profilage du code avec le profil j'ai eu la suivante (échantillons, de pourcentage, de code):
58228 32.1263 : sum += (double)i * frame->histo[i];
116760 64.4204 : sumsquared += (double)(i * i) * frame->histo[i];
ou, la première ligne prend en hausse de 32% de temps CPU, le deuxième ligne de 64%.
J'ai fait un peu de benchmarking et il semble être le type de données/moulage de cette problématique. Quand j'ai modifier le code pour
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];
}
il s'exécute ~10x plus rapide. Cependant, ce gain de performance varie également par la plate-forme. Sur la station de travail, un Core i7 950 @ 3.07 GHz, le code est 10 fois plus rapide. Sur mon Macbook8,1, qui dispose d'un processeur Intel Core i7 Sandy Bridge 2,7 GHz (2620M) le code n'est 2x plus rapide.
Maintenant je me demande:
- Pourquoi le code d'origine de la lenteur et facilement accéléré?
- Pourquoi cela varie selon la plate-forme autant?
Mise à jour:
J'ai compilé le code ci-dessus avec
g++ -O3 -Wall cast_test.cc -o cast_test
Update2:
J'ai couru à l'optimisation des codes par le biais d'un générateur de profils (Instruments sur Mac, comme les Requins) et a constaté deux choses:
1) La boucle elle-même prend une quantité considérable de temps dans certains cas. thismaxval
est de type size_t
.
-
for(size_t i = 0; i < thismaxval; i++)
17% du total de mes runtime -
for(uint_fast32_t i = 0; i < thismaxval; i++)
prend de 3,5% -
for(int i = 0; i < thismaxval; i++)
ne s'affiche pas dans le profiler, je suppose que c'est moins de 0,1%
2) Les types de données et de la coulée d'importance comme suit:
-
sumsquared += (double)(i * i) * histo[i];
15% (avecsize_t i
) -
sumsquared += (double)(i * i) * histo[i];
36% (uint_fast32_t i
) -
isumsquared += (i * i) * histo[i];
13% (avecuint_fast32_t i
,uint_fast64_t isumsquared
) -
isumsquared += (i * i) * histo[i];
11% (avecint i
,uint_fast64_t isumsquared
)
Étonnamment, int
plus rapide que de l' uint_fast32_t
?
Update4:
J'ai couru quelques plus de tests avec différents types de données et les différents compilateurs, sur une seule machine. Les résultats sont comme suit.
Pour testd 0 -- 2 du code correspondant est
for(loop_t i = 0; i < thismaxval; i++)
sumsquared += (double)(i * i) * histo[i];
avec sumsquared
d'un lit double, et loop_t
size_t
, uint_fast32_t
et int
pour les tests de 0, 1 et 2.
Pour les tests 3--5 du code de l'est
for(loop_t i = 0; i < thismaxval; i++)
isumsquared += (i * i) * histo[i];
avec isumsquared
de type uint_fast64_t
et loop_t
nouveau size_t
, uint_fast32_t
et int
pour les tests, 3, 4 et 5.
Les compilateurs que j'ai utilisées sont gcc 4.2.1, gcc 4.4.7, gcc et gcc 4.6.3 4.7.0. Les horaires sont en pourcentages du total des temps cpu du code, de façon à montrer la performance relative, pas absolue (bien que l'exécution a été assez constant à 21). Le temps de calcul est pour les deux lignes, parce que je ne suis pas sûr si le profiler correctement séparé les deux lignes de code.
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 de 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
ou:
Sur cette base, il semble que le casting est cher, indépendamment de ce type entier que j'utilise.
Aussi, il semble gcc 4.6 et 4.7 sont pas en mesure d'optimiser la boucle 3 (size_t et uint_fast64_t) correctement.