Décaler les bits vers la gauche et vers la droite est apparemment plus rapide que les opérations de multiplication et de division sur la plupart, peut-être même tous, des CPU si vous utilisez une puissance de 2. Cependant, cela peut réduire la clarté du code pour certains lecteurs et certains algorithmes. Le transfert de bits est-il vraiment nécessaire pour les performances, ou puis-je m'attendre à ce que le compilateur ou VM remarque le cas et l'optimise (en particulier, lorsque la puissance de 2 est un littéral)? I Je suis principalement intéressé par le comportement de Java et .NET, mais nous nous félicitons également des informations sur les autres implémentations de langage.
Aujourd'hui, la plupart des compilateurs font plus que convertir, multiplier ou diviser par une puissance de deux pour effectuer des opérations de décalage. Lors de l'optimisation, de nombreux compilateurs peuvent optimiser une multiplication ou une division avec une constante de temps de compilation, même si ce n'est pas une puissance de 2. Souvent, une multiplication ou une division peut être décomposée en une série de décalages et d'ajouts, et si cette série d'opérations sera plus rapide que la multiplication ou la division, le compilateur l'utilisera.
Pour la division par une constante, le compilateur peut souvent convertir l'opération en une multiplication par un "nombre magique" suivi d'un décalage. Cela peut être un économiseur de cycle d'horloge majeur car la multiplication est souvent beaucoup plus rapide qu'une opération de division.
Le livre de Henry Warren, Hacker's Delight, a une mine d'informations sur ce sujet, qui est également assez bien couvert sur le site Web compagnon:
Voir aussi une discussion (avec un lien ou deux) dans:
Quoi qu'il en soit, tout cela se résume à permettre au compilateur de prendre soin des détails fastidieux des micro-optimisations. Cela fait des années que faire vos propres quarts a déjoué le compilateur.
Presque n'importe quel environnement digne de ce nom optimisera cela pour vous. Et si ce n'est pas le cas, vous avez de plus gros poissons à faire frire. Sérieusement, ne perdez pas une seconde de réflexion à ce sujet. Vous saurez quand vous avez des problèmes de performances. Et après avoir exécuté un profileur, vous saurez ce qui en est la cause, et il devrait être assez clair comment réparer.
Vous n'entendrez jamais personne dire "ma demande était trop lente, alors j'ai commencé à remplacer aléatoirement x * 2
avec x << 1
et tout a été corrigé! "Les problèmes de performances sont généralement résolus en trouvant un moyen de faire un travail moins important, et non en trouvant un moyen de faire le même travail 1% plus rapidement.
Les humains ont tort dans ces cas.
99% quand ils essaient de deviner un compilateur moderne (et tous les futurs).
99,9% lorsqu'ils essaient de deviner les JIT modernes (et tous les futurs) en même temps.
99,999% lorsqu'ils tentent de deviner les optimisations de processeur modernes (et futures).
Programmez d'une manière qui décrit précisément ce que vous voulez accomplir, pas comment le faire. Les futures versions de JIT, VM, compilateur et CPU peuvent toutes être améliorées et optimisées indépendamment. Si vous spécifiez quelque chose d'aussi petit et spécifique, vous perdez le bénéfice de toutes les optimisations futures.
Vous pouvez presque certainement compter sur l'optimisation de la multiplication de la puissance littérale de deux pour une opération de décalage. C'est l'une des premières optimisations que les étudiants en construction de compilateurs apprendront. :)
Cependant, je ne pense pas qu'il y ait de garantie pour cela. Votre code source doit refléter votre intention, plutôt que d'essayer de dire à l'optimiseur quoi faire. Si vous augmentez la quantité, utilisez la multiplication. Si vous déplacez un champ de bits d'un endroit à un autre (pensez à la manipulation des couleurs RVB), utilisez une opération de décalage. Quoi qu'il en soit, votre code source reflétera ce que vous faites réellement.
Notez que le déplacement vers le bas et la division donneront (en Java, certainement) des résultats différents pour les nombres négatifs et impairs.
int a = -7;
System.out.println("Shift: "+(a >> 1));
System.out.println("Div: "+(a / 2));
Tirages:
Shift: -4
Div: -3
Puisque Java n'a pas de nombres non signés, il n'est pas vraiment possible pour un compilateur Java d'optimiser cela).
Sur les ordinateurs que j'ai testés, les divisions entières sont 4 à 10 fois plus lentes que les autres opérations.
Lorsque les compilateurs peuvent remplacer des divisions par des multiples de 2 et ne faire aucune différence, les divisions par des multiples non de 2 sont considérablement plus lentes.
Par exemple, j'ai un programme (graphique) avec beaucoup de nombreuses divisions par 255. En fait, mon calcul est:
r = (((top.R - bottom.R) * alpha + (bottom.R * 255)) * 0x8081) >> 23;
Je peux m'assurer que c'est beaucoup plus rapide que mon calcul précédent:
r = ((top.R - bottom.R) * alpha + (bottom.R * 255)) / 255;
donc non, les compilateurs ne peuvent pas faire toutes les astuces d'optimisation.
Je demanderais "qu'est-ce que tu fais pour que ce soit important?". Concevez d'abord votre code pour la lisibilité et la maintenabilité. La probabilité que la multiplication standard des décalages de bits fasse une différence de performance est EXTRÊMEMENT petite.
Cela dépend du matériel. Si nous parlons de micro-contrôleur ou d'i386, alors le décalage peut être plus rapide mais, comme l'indiquent plusieurs réponses, votre compilateur fera généralement l'optimisation pour vous.
Sur le matériel moderne (Pentium Pro et au-delà), le pipelining rend cela totalement hors de propos et s'écartant des sentiers battus signifie généralement que vous perdez beaucoup plus d'optimisations que vous ne pouvez en gagner.
Les micro-optimisations ne sont pas seulement une perte de temps, elles sont également extrêmement difficiles à réaliser.
Selon les résultats de ce microbenchmark , le décalage est deux fois plus rapide que la division (Oracle Java 1.7.0_72).
Si le compilateur (constante de compilation) ou JIT (constante d'exécution) sait que le diviseur ou le multiplicande est une puissance de deux et qu'une arithmétique entière est effectuée, il la convertira en décalage pour vous.
Je suis stupéfait car je viens d'écrire ce code et j'ai réalisé que le décalage d'un est en fait plus lent que la multiplication par 2!
(EDIT: a changé le code pour arrêter de déborder après la suggestion de Michael Myers, mais les résultats sont les mêmes! Qu'est-ce qui ne va pas ici?)
import Java.util.Date;
public class Test {
public static void main(String[] args) {
Date before = new Date();
for (int j = 1; j < 50000000; j++) {
int a = 1 ;
for (int i = 0; i< 10; i++){
a *=2;
}
}
Date after = new Date();
System.out.println("Multiplying " + (after.getTime()-before.getTime()) + " milliseconds");
before = new Date();
for (int j = 1; j < 50000000; j++) {
int a = 1 ;
for (int i = 0; i< 10; i++){
a = a << 1;
}
}
after = new Date();
System.out.println("Shifting " + (after.getTime()-before.getTime()) + " milliseconds");
}
}
Les résultats sont:
Multipliant 639 millisecondes
Décalage de 718 millisecondes
La plupart des compilateurs transformeront la multiplication et la division en décalages de bits lorsque cela est approprié. C'est l'une des optimisations les plus faciles à faire. Vous devez donc faire ce qui est plus facilement lisible et approprié à la tâche donnée.