11
votes

Quelle est la différence entre les opérations de changement de bits et arithmétiques?

int aNumber;

aNumber = aValue / 2;
aNumber = aValue >> 1;

aNumber = aValue * 2;
aNumber = aValue << 1;

aNumber = aValue / 4;
aNumber = aValue >> 2;

aNumber = aValue * 8;
aNumber = aValue << 3;

// etc.
Whats is the "best" way to do operations? When is better to use bit shifting?

7 commentaires

Question additionnelle: se comportent-ils la même chose en cas de débordement arithmétique?


Je ne pense pas très bien que BitShifting fonctionne très bien avec des nombres négatifs (ou cela pourrait ne pas fonctionner comme prévu).


Souvent (toujours?) Les processeurs ont des changements arithmétiques, mais je ne sais pas si C compilateurs compiler différemment si les opérandes sont signés ou non.


BitShifting fonctionne parfaitement bien avec les nombres négatifs et le complément de deux.


Dans votre dernier exemple, vous le faites mal. Il devrait être Avalue * 8 == Avalue << 3 Mais vous êtes bossé un trop de fois.


@David Titutarenco, décalage logique ne se comporte pas bien avec des nombres négatifs; par exemple. Sur la valeur 8 bits: 1010_0000 BitShifted Logiquement vers la gauche de 1 devient 0100_0000 qui est un nombre positif ... Le changement d'arithmétique est un peu différent et fonctionne bien pour les nombres négatifs.


une erreur * 8 n'est pas la même que << 4 C'est << 3


10 Réponses :


19
votes

Les deux sont fonctionnellement équivalents dans les exemples que vous avez donnés (à l'exception de la finale, qui devrait lire Avalue * 8 == Avalue << 3 ), si vous utilisez positif entiers . Ceci est seul le cas lors de la multiplication ou de la division par des pouvoirs de 2.

Le changement de bits n'est jamais plus lent que l'arithmétique. En fonction de votre compilateur, la version arithmétique peut être compilée à la version à décalage de bits, auquel cas ils sont aussi efficaces. Sinon, le changement de bit doit être significativement plus rapide que l'arithmétique.

La version arithmétique est souvent plus lisible. Par conséquent, j'utilise la version arithmétique dans presque tous les cas, et utilise uniquement le changement de bit si le profilage révèle que la déclaration est dans un goulot d'étranglement:

Les programmes doivent être écrits pour que les personnes à lire, et uniquement pour les machines à exécuter.


6 commentaires

D'accord. Commencez avec l'arithmétique jusqu'à ce que votre code fonctionne et que votre code est sans bug. puis digue et utilisez le décalage de bits comme l'un des outils de votre sac à outils pour optimiser les performances.


De plus, vous voudrez peut-être regarder la sortie d'assemblage optimisée. Je pense que certains compilateurs traduiront des arithmétiques simples aux changements lors de l'optimisation.


Pour des trucs simples, un compilateur peut le trouver et optimiser. Mais ce que cela ne fera pas, c'est modifier votre algorithme avec bit-d'fois. Et pour cela, vous devez mettre votre chapeau binaire.


Un bon exemple de ce type d'optimisations sont des bibliothèques de mathématiques hautes performances. J'ai vu des situations où la division a été ramifiée à l'aide d'une déclaration IF - si le diviseur était un pouvoir de deux, il irait dans la branche changeante, sinon utiliserait des arithmétiques régulières. Bien sûr, cela n'a aucun sens que des divisions répétées avec le même diviseur (dans un algorithme, etc.).


Je pense que cela est également un bon exemple de la raison pour laquelle vous devriez aller plus souvent pour la version arithmétique compte tenu de l'erreur de la question. Plus de pensée est nécessaire pour obtenir votre bit de changement de droite ...


et utilisez uniquement le changement de bits si le profilage révèle que l'instruction est dans un goulot d'étranglement . C'est un grand exemple d'exemple d'un piège commun. Optimisation prématurée! Si ce n'est pas cassé, ne le répare pas :)



0
votes

Si vous avez de gros calculs dans une sorte d'environnement de boucle serrée où la vitesse de calcul a une incidence --- Utiliser des opérations de bits. (considéré plus rapide que les opérations arithmétiques)


2 commentaires

C'est une optimisation prématurée (qui est la racine de tout mal). Utilisez l'expression naturelle de ce que vous faites. Si vous divisez logiquement par 8 (par exemple), utilisez l'opération arithmétique. Si vous essayez logiquement de déplacer les changements de bits d'utilisation des bas-octets. La raison en est que les compilateurs modernes convertissent automatiquement multiplier et diviseront par des pouvoirs de deux en opérations de changement pour vous.


Les opérations de bits peuvent être considérées comme plus rapides comme opérations de machine sur certaines plates-formes matérielles. Ils sont pas "considérés comme plus rapides" ni comme c ni opérations de niveau de langue c ++. Ceux qui font ce genre de "envisagent" de confondre simplement les opérations de la machine avec des opérations linguistiques.



0
votes

Quand il s'agit d'un nombre de puissance 2 (2 ^ x), il est préférable d'utiliser des changements - il suffit de "pousser" les bits. (1 opération de montage au lieu de 2 dans la division).

Y a-t-il une langue que son compilateur fait cette optimisation?


5 commentaires

Sur quelle plate-forme est-il intégré Deux instructions de montage?


Consultez mon commentaire à la réponse d'AJ.


@Pete sur 6502 Entier Divide est de nombreuses autres opérations - il n'y a pas de fracture entier. Néanmoins, je ne suis pas au courant de l'OGF un compilateur de l'objectif-C pour 6502, il est donc probablement pas pertinent.


Tous les processeurs que je connais / ont vu avoir une seule division d'instructions. Le problème est que la division est plus lente que celle de décalage, pas que cela soit effectué par une autre instruction. Le changement d'un registre est plus rapide car les circuits impliqués sont très simples, tandis que ceux d'une division sont beaucoup plus complexes.


@Shintakezzou: Rappelez-vous les bonnes journées ou les processeurs à l'ancienne ou tout simplement encore utilisé (où?) Division - manque de processeurs ... :) ... Je pense que tous les processeurs actuellement utilisés dans des appareils de tous les jours (sauf les machines à laver et similaires ) avoir l'instruction div



4
votes

Le changement de bits est une opération «proche de l'métal» que la plupart du temps ne contient aucune information sur ce que vous voulez vraiment atteindre.

Si vous souhaitez diviser un numéro par deux, par tous les moyens, écrivez x / 2 . Il arrive d'être atteint par x >> 1 , mais ce dernier cache l'intention.

Quand cela s'avère devenir un goulot d'étranglement, réviser le code.


0 commentaires

8
votes

La différence est que les opérations arithmétiques ont clairement défini des résultats (à moins qu'ils ne gèrent pas de débordement signé, c'est-à-dire). Les opérations de décalage n'ont pas de résultats définis dans de nombreux cas. Ils sont clairement définis pour les types non signés en C et C ++, mais avec des types signés, les choses deviennent rapidement difficiles.

Dans la langue C ++, la signification arithmétique de la gauche-shift <<< / code> pour les types signés n'est pas défini. Il fait juste déplacer des morceaux, remplissant des zéros à droite. Ce que cela signifie en sens arithmétique dépend de la représentation signée utilisée par la plate-forme. Pratiquement la même chose est vraie pour le décalage de droite >> . Les valeurs négatives de changement de droite conduisent à des résultats définis par la mise en œuvre.

Dans C, les choses du langage C sont définies légèrement différemment. Les valeurs négatives de déplacement gauche sont impossibles: cela conduit à un comportement indéfini. Les valeurs négatives de changement de droite conduisent à des résultats définis par la mise en œuvre.

sur la plupart des implémentations pratiques Chaque station droite effectue une division par 2 avec arrondissement vers l'infini négatif. Ceci, BTW, est notamment différent de la division arithmétique / par 2, puisque typiquement (et toujours en C99) du temps autour de 0.

Tant que lorsque vous devriez utiliser un bit-shifting ... Le changement de bit est destiné aux opérations qui fonctionnent sur des bits. Les opérateurs de changement de bits sont très rarement utilisés comme remplacement des opérateurs arithmétiques (par exemple, vous ne devez jamais utiliser les changements pour effectuer la multiplication / division par constante).


2 commentaires

Surtout que le compilateur est très susceptible d'optimiser votre opération arithmétique avec des opérateurs de bit-shifts si vous utilisez des constantes.


@Matthieu M.: Il est encore plus probable que le compilateur l'optimise avec des opérations qui ne sont ni immédiatement arithmétiques ni changements.



1
votes

Tant que vous multipliez ou divisez dans les 2er puissances, il est plus rapide de fonctionner avec un décalage car il s'agit d'une seule opération (nécessite un seul cycle de processus).
On s'habitue à lire << 1 AS * 2 et >> 2 AS / 4 AS / 4 assez rapidement, donc je ne suis pas d'accord avec la lisibilité à l'écart lorsque vous utilisez le changement, mais cela est à la hauteur de chaque personne.

Si vous voulez en savoir plus sur la façon dont et pourquoi, peut-être que Wikipedia peut aider ou si vous souhaitez passer par la douleur apprendre l'assemblage; -)


0 commentaires

2
votes

Quel est le "meilleur" moyen de faire des opérations?

Utilisez des opérations arithmétiques lors de la gestion des chiffres. Utiliser des opérations de bits lors de la gestion des bits. Période. C'est un bon sens. Je doute que quiconque penserait jamais à utiliser des opérations de changement de bits pour les INT ou les doubles en tant que quotidienne régulière est une bonne idée.

Quand est préférable d'utiliser un bit changeant?

Lorsque vous traitez avec des bits?

Question supplémentaire: se comportent-ils la même chose en cas de débordement arithmétique?

Oui. Les opérations arithmétiques appropriées sont (souvent, mais pas toujours) simplifiées à leurs homologues de la plupart des compilateurs modernes.

edit : la réponse a été acceptée, mais je veux juste ajouter qu'il y a une tonne de mauvais avis dans cette question. Vous ne devriez jamais (lire: presque jamais) utiliser des opérations de décalage de bits lorsqu'il s'agit d'INTS. C'est une pratique horrible.


0 commentaires

2
votes

Lorsque votre objectif est de multiplier certains nombres, utiliser des opérateurs arithmétiques est logique.

Lorsque vos objectifs consistent à déplacer logiquement les bits, utilisez les opérateurs de décalage.

Par exemple, dites-vous Sactez les composants RVB d'un mot RVB, ce code a du sens: xxx

d'autre part lorsque vous souhaitez multiplier une certaine valeur avec 4, vous devez vraiment coder votre intention, et ne pas faire des changements.


0 commentaires

1
votes

Par exemple des différences, il s'agit de X86 Assembly créé à l'aide de GCC 4.4 avec -O3

aNumber = aValue * 8;
aNumber = aValue << 4;


0 commentaires

0
votes
int i = -11;
std::cout << (i  / 2) << '\n';   // prints -5 (well defined by the standard)
std::cout << (i >> 1) << '\n';   // prints -6 (may differ on other platform)
Depending on the desired rounding behavior, you may prefer one over the other.

0 commentaires