J'ai une fonction qui trouve la prochaine puissance de deux pour un entier donné. Si l'entier est une puissance de deux, il renvoie le pouvoir.Pourquoi le compilateur C++ ne parvient-il pas à optimiser "if (test) - foo" à "foo - = test"?
assez simple:
char nextpow2if(int a)
{
char foo = char(32 - __builtin_clz(a));
bool ispow2 = !(a & a-1);
if (ispow2) --foo;
return foo;
}
Cependant, après la compilation avec gcc 6 avec -O2, après avoir inspecté l'ensemble généré, je vois que cela est compilé avec l'instruction apparemment inutile cmovne
après avoir calculé le foo-1. Pire encore avec gcc5 et plus je reçois une branche réelle jne
dans le code.
La façon plus rapide de compiler ce serait comme si je l'avais écrit la fonction suivante:
char nextpow2sub(int a)
{
char foo = char(32 - __builtin_clz(a));
bool ispow2 = !(a & a-1);
return foo - ispow2;
}
Ce code est compilé correctement par tous les compilateurs les plus brefs (et plus rapide) l'assemblage possible avec un sete
et la soustraction pour le bool.
Pourquoi le compilateur ne parvient-il pas à optimiser le premier? Cela semble être un cas très facile à identifier. Pourquoi gcc 5 et plus compilent ceci à une branche réelle jne
? Y a-t-il un cas limite entre les deux versions, que je ne peux pas voir, qui pourrait les amener à se comporter différemment?
PS: Démo en direct here
Edit: Je n'ai pas testé les performances avec gcc 6 mais avec gcc 5 celui-ci est environ deux fois plus rapide (bien sur un test de PerformanSe synthétique, au moins). C'est ce qui m'a amené à poser cette question.
Ne pas spammer des tags pour des langues sans rapport! – Olaf
* "Le moyen le plus rapide de compiler ceci serait comme si j'avais écrit la fonction suivante:" * Avez-vous mesuré cela? Combien est-ce que c'est plus rapide? –
Comparez-vous la performance par le nombre de code d'assemblage généré? Ce n'est pas un bon moyen de le faire (même si cela peut être vrai dans certains cas). – Arunmu