46 votes

L'instruction `if` est-elle redondante avant modulo et avant les opérations d'assignation?

Envisager de code suivant:

unsigned idx;
//.. some work with idx
if( idx >= idx_max )
    idx %= idx_max;

Pourrait être simplifié pour seulement la deuxième ligne:

idx %= idx_max;

et permettra d'atteindre le même résultat.


Plusieurs fois, j'ai rencontré de code suivant:

unsigned x;
//... some work with x
if( x!=0 )
  x=0;

Pourrait être simplifiée

x=0;

Les questions:

  • Il n'est pas logique d'utiliser if et pourquoi? Surtout avec les BRAS Manette de jeu d'instructions.
  • Ces ifs être omis?
  • Ce que l'optimisation ne compilateur?

66voto

Nir Friedman Points 3165

Si vous voulez comprendre ce que le compilateur fait, vous aurez juste besoin de tirer vers le haut un peu de montage. Je recommande ce site (j'ai déjà entrés dans le code de la question)): https://godbolt.org/g/FwZZOb.

Le premier exemple est plus intéressant.

int div(unsigned int num, unsigned int num2) {
    if( num >= num2 ) return num % num2;
    return num;
}

int div2(unsigned int num, unsigned int num2) {
    return num % num2;
}

Génère:

div(unsigned int, unsigned int):          # @div(unsigned int, unsigned int)
        mov     eax, edi
        cmp     eax, esi
        jb      .LBB0_2
        xor     edx, edx
        div     esi
        mov     eax, edx
.LBB0_2:
        ret

div2(unsigned int, unsigned int):         # @div2(unsigned int, unsigned int)
        xor     edx, edx
        mov     eax, edi
        div     esi
        mov     eax, edx
        ret

Fondamentalement, le compilateur ne pas optimiser loin la branche, pour des cas très particuliers et les raisons logiques. Si la division entière était environ le même coût que la comparaison, puis la branche serait assez inutile. Mais la division entière (dont le module est réalisée avec le plus souvent) est en fait très cher: http://www.agner.org/optimize/instruction_tables.pdf. Les chiffres varient grandement d'une architecture et d'entier de taille, mais généralement, il pourrait être un temps de latence de n'importe où de 15 à près de 100 cycles.

En prenant une branche avant d'effectuer le module, vous pouvez vous épargner beaucoup de travail. Avis: le compilateur également de ne pas transformer le code sans une branche en branche au niveau de l'assemblage. C'est parce que la direction a un inconvénient: si le module finit par être nécessaire de toute façon, vous avez juste perdu un peu de temps.

Il n'y a pas moyen de prendre une décision raisonnable, la bonne optimisation sans connaître la fréquence relative avec laquelle idx < idx_max sera vrai. Si les compilateurs (gcc et clang faire la même chose) optez pour la carte la code dans un relativement transparent, laissant ce choix dans les mains du développeur.

De sorte que la direction aurait pu être un très bon choix.

La deuxième branche devrait être complètement inutile, parce que la comparaison et l'attribution sont d'un coût comparable. Cela dit, vous pouvez le voir dans le lien que les compilateurs ne sera toujours pas effectuer cette optimisation s'ils ont une référence à la variable. Si la valeur est une variable locale (comme dans votre démontré code), alors le compilateur d'optimiser la branche à l'écart.

En somme, le premier morceau de code est peut-être une optimisation raisonnable, la seconde, sans doute fatigué programmeur.

6voto

supercat Points 25534

Il y a un certain nombre de situations où l'écriture d'une variable avec une valeur il détient déjà peut être plus lente que la lecture, la découverte détient déjà la valeur souhaitée, et le saut de l'écriture. Certains systèmes ont un cache de processeur qui envoie toutes les demandes d'écriture dans la mémoire. Bien que de telles conceptions ne sont pas monnaie courante aujourd'hui, ils ont utilisé pour être tout à fait courants, car ils peuvent offrir une fraction substantielle de la performance boost complet en lecture/écriture de la mise en cache peut offrir, mais à une petite fraction du coût.

Le Code ci-dessus peut également être pertinente dans certains multi-CPU situations. Le plus courant d'une telle situation serait lorsque le code s'exécutant simultanément sur deux ou plusieurs cœurs de PROCESSEUR sera à plusieurs reprises de frapper la variable. Dans un multi-core système de mise en cache avec un solide modèle de mémoire, un noyau qui veut écrire une variable doit d'abord négocier avec d'autres noyaux d'acquérir la propriété exclusive de la ligne de cache contenant, et doit alors négocier de nouveau à renoncer à un tel contrôle, la prochaine fois de toute autre base veut lire ou écrire. De telles opérations sont susceptibles d'être très cher, et les frais seront à la charge même si chaque écriture est tout simplement de stocker la valeur de stockage déjà jugé. Si l'emplacement est à zéro et n'est jamais écrit à nouveau, cependant, les deux cœurs sont capables de tenir la ligne de cache simultanément pour les non-exclusif d'accès en lecture seule et ne jamais avoir à négocier d'autres.

Dans presque toutes les situations où plusieurs Processeurs pourrait être frappé dans une variable, la variable doit être déclarée volatile. La seule exception, ce qui pourrait être applicable ici, serait dans le cas où toutes les écritures à une variable qui se produisent après le début de l' main() va stocker la même valeur, et le code serait se comporter correctement si oui ou non un magasin par un PROCESSEUR était visible dans un autre. Si certains l'opération plusieurs fois serait du gaspillage, mais sans danger, et le but de la variable est-à-dire qu'il doit être fait, de nombreuses implémentations peuvent être en mesure de générer un code de meilleure qualité sans l' volatile qualificatif que, à la condition qu'ils ne cherchent pas à améliorer l'efficacité en faisant de l'écriture inconditionnel.

Par ailleurs, si l'objet étaient accessibles via un pointeur, il y aurait un autre raison possible pour le code ci-dessus: si une fonction est conçue pour accepter un const objet où un certain champ est zéro, ou un non-const objet qui devraient avoir ce champ est défini sur zéro, le code ci-dessus peut être nécessaire de veiller à comportement défini dans les deux cas.

2voto

metamorphosis Points 834

Ce qui concerne le premier bloc de code: c'est un micro-optimisation basée sur Chandler Carruth recommandations pour Clang (voir ici pour plus d'infos), toutefois, il ne faut pas nécessairement qu'il serait valable de micro-optimisation de cette forme (en utilisant, si plutôt que de ternaire) ou sur le compilateur.

Modulo est un raisonnablement opération coûteuse, si le code est exécuté souvent et il y a une forte statistique pencher d'un côté ou de l'autre du conditionnel, le CPU de la direction de la prévision (étant donné un PROCESSEUR récent) permettra de réduire considérablement le coût de la direction générale de l'enseignement.

1voto

CodeLurker Points 6

Il semble une mauvaise idée d'utiliser le si là, pour moi.

Vous êtes de droite. Si oui ou non idx >= idx_max, il sera sous idx_max après idx %= idx_max. Si idx < idx_max, il sera inchangé, si le si est suivi ou pas.

Alors que vous pourriez penser ramification autour de la modulo peut gagner du temps, le vrai coupable, je dirais, c'est que quand les branches sont suivies, le pipelining moderne CPU avoir à réinitialiser leur pipeline, et que les coûts d'un parent beaucoup de temps. Mieux pour ne pas avoir à suivre une direction, que d'en faire un entier modulo, qui coûte à peu près autant de temps qu'une division entière.

EDIT: Il s'avère que le module est assez lent vs la direction, comme suggéré par d'autres ici. Voici un type de l'examen de cette exacte même question: CppCon 2015: Chandler Carruth "Tuning C++: des points de repère, et les Processeurs, et les Compilateurs! Oh Mon Dieu!" (suggéré dans un autre DONC, la question liée à une autre réponse à cette question).

Ce mec écrit des compilateurs, et pense qu'il serait plus rapide sans la direction générale; mais ses repères lui a donné tort. Même lorsque la direction a pris seulement 20% du temps, elle a testé plus rapide.

Une autre raison de ne pas le si: Un de moins de ligne de code à maintenir, et pour quelqu'un d'autre pour comprendre ce qu'il signifie. Le gars dans le lien ci-dessus a réellement créé un "plus vite module de macro". À mon humble avis, ce ou une fonction en ligne est le chemin à parcourir pour la performance des applications critiques, parce que votre code sera tellement plus compréhensible sans la branche, mais s'exécutera aussi rapidement.

Enfin, le gars dans la vidéo ci-dessus est l'intention de faire cette optimisation connu pour les rédacteurs du compilateur. Ainsi, les si seront probablement ajoutés pour vous, si ce n'est dans le code. Par conséquent, tout le mod seront les seuls à le faire, si cela vient à propos.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X