J'ai un très étrange comportement du compilateur G++ tire des calculs dans une chaude de boucle, sévèrement réduire les performances du code résultant. Ce qui se passe ici?
Prenons cette fonction:
#include <cstdint>
constexpr bool noLambda = true;
void funnyEval(const uint8_t* columnData, uint64_t dataOffset, uint64_t dictOffset, int32_t iter, int32_t limit, int32_t* writer,const int32_t* dictPtr2){
// Computation X1
const int32_t* dictPtr = reinterpret_cast<const int32_t*>(columnData + dictOffset);
// Computation X2
const uint16_t* data = (const uint16_t*)(columnData + dataOffset);
// 1. The less broken solution without lambda
if (noLambda) {
for (;iter != limit;++iter){
int32_t t=dictPtr[data[iter]];
*writer = t;
writer++;
}
}
// 2. The totally broken solution with lambda
else {
auto loop = [=](auto body) mutable { for (;iter != limit;++iter){ body(iter); } };
loop([=](unsigned index) mutable {
int32_t t=dictPtr[data[index]];
*writer = t;
writer++;
});
}
}
Le problème ici est que G++ en quelque sorte adore tirer les calculs X1
et X2
dans le chaud boucle principale, la réduction de la performance. Voici les détails:
La fonction simplement parcourt un tableau data
, recherche une valeur dans un dictionnaire, dictPtr
et l'écrit sur une cible à l'emplacement de la mémoire writer
.
data
et dictPtr
sont calculées au début de la fonction. Il dispose de deux saveurs pour le faire: l'une avec un lambda, l'autre sans.
(Notez que cette fonction est seulement un minimum de travail exemple de beaucoup plus compliqué code. Merci donc de s'abstenir de tout commentaire que le lambda est inutile ici. Je suis conscient de ce fait et dans le code d'origine, il est nécessaire, malheureusement.)
Le problème lors de la compilation avec la dernière g++ (essayé 8.1 et 7.2, même problème avec les anciens g++s comme vous pouvez le voir dans la godbolt liens fournis) avec le haut niveau d'optimisation (-O3 -std=c++14
) est la suivante:
La Solution 2. (noLambda=false
) génère de très mauvais code de la boucle, encore pire que la "naïve" de la solution, car il suppose que c'est une bonne idée de retirer les Calculs X1 et X2, qui sont en dehors de la super chaud boucle principale, dans le super chaud boucle principale, faisant environ 25% plus lent sur mon CPU.
.L3:
movl %ecx, %eax # unnecessary extra work
addl $1, %ecx
addq $4, %r9 # separate loop counter (pointer increment)
leaq (%rdi,%rax,2), %rax # array indexing with an LEA
movzwl (%rax,%rsi), %eax # rax+rsi is Computation X2, pulled into the loop!
leaq (%rdi,%rax,4), %rax # rax+rdx is Computation X1, pulled into the loop!
movl (%rax,%rdx), %eax
movl %eax, -4(%r9)
cmpl %ecx, %r8d
jne .L3
Lors de l'utilisation d'une habitude pour la boucle (noLambda=true
), puis le code est mieux que X2 n'est plus tiré dans la boucle, mais X1 est toujours!:
.L3:
movzwl (%rsi,%rax,2), %ecx
leaq (%rdi,%rcx,4), %rcx
movl (%rcx,%rdx), %ecx # This is Computation X1, pulled into the loop!
movl %ecx, (%r9,%rax,4)
addq $1, %rax
cmpq %rax, %r8
jne .L3
Vous pouvez essayer, c'est vraiment X1 dans la boucle en remplaçant dictPtr
(le calcul X1) dans la boucle avec la dictPtr2
(un paramètre), l'instruction va disparaître:
.L3:
movzwl (%rdi,%rax,2), %ecx
movl (%r10,%rcx,4), %ecx
movl %ecx, (%r9,%rax,4)
addq $1, %rax
cmpq %rax, %rdx
jne .L3
C'est finalement la boucle que j'ai envie de l'avoir. Une simple boucle qui charge les valeurs et stocke le résultat sans aléatoires tirant les calculs en elle.
Donc, ce qui se passe ici? C'est rarement une bonne idée de tirer un calcul dans une chaude en boucle, mais G++ semble le penser ici. Cela me coûte réelle de la performance. Le lambda aggrave la situation dans son ensemble; elle conduit G++ pour tirer encore plus de calculs dans la boucle.
Ce qui rend ce problème si grave, c'est que c'est vraiment trivial de code C++ sans caractéristiques de fantaisie. Si je ne peux pas compter sur mon compilateur produisant parfait code pour un exemple trivial, j'ai besoin de vérifier l'assemblée de toutes les boucles dans mon code pour s'assurer que tout est aussi rapide comme il pourrait l'être. Cela signifie également qu'il y a probablement un grand nombre de programmes concernés par cette.