Supposons que j'ai le code C suivant:
int i = 5;
int j = 10;
int result = i + j;
Si je boucle plusieurs fois, serait-il plus rapide d'utiliser int result = 5 + 10
? Je crée souvent des variables temporaires pour rendre mon code plus lisible, par exemple, si les deux variables ont été obtenues à partir d'un tableau en utilisant une expression longue pour calculer les indices. Est-ce une mauvaise performance en C? Et les autres langues?
Un compilateur d'optimisation moderne devrait optimiser ces variables, par exemple si nous utilisons l'exemple suivant dans godbolt avec gcc
en utilisant le -std=c99 -O3
drapeaux (voir en direct):
#include <stdio.h>
void func()
{
int i = 5;
int j = 10;
int result = i + j;
printf( "%d\n", result ) ;
}
il en résultera l'Assemblée suivante:
movl $15, %esi
pour le calcul de i + j
, c'est une forme de propagation constante .
Remarque, j'ai ajouté le printf
afin d'avoir un effet secondaire, sinon func
aurait été optimisé pour:
func:
rep ret
Ces optimisations sont autorisées sous la règle comme si , qui ne nécessite que le compilateur pour émuler le comportement observable d'un programme. Ceci est traité dans le projet de norme C99, section 5.1.2.3
Exécution du programme qui dit:
Dans la machine abstraite, toutes les expressions sont évaluées comme spécifié par la sémantique. Une implémentation réelle n'a pas besoin d'évaluer une partie d'une expression si elle peut en déduire que sa valeur n'est pas utilisée et qu'aucun effet secondaire nécessaire n'est produit (y compris ceux provoqués par l'appel d'une fonction ou l'accès à un objet volatil).
Voir aussi: Optimisation du code C++: pliage constant
Il s'agit d'une tâche facile à optimiser pour un compilateur d'optimisation. Il supprimera toutes les variables et remplacera result
par 15
.
Le pliage constant en forme SSA est à peu près l'optimisation la plus fondamentale qui soit.
L'exemple que vous avez donné est facile à optimiser pour un compilateur. L'utilisation de variables locales pour mettre en cache les valeurs extraites des structures et des tableaux globaux peut en fait accélérer l'exécution de votre code. Si, par exemple, vous récupérez quelque chose dans une structure complexe à l'intérieur d'une boucle for où le compilateur ne peut pas optimiser et que vous savez que la valeur ne change pas, les variables locales peuvent gagner un peu de temps.
Vous pouvez utiliser GCC (d'autres compilateurs également) pour générer le code d'assemblage intermédiaire et voir ce que le compilateur fait réellement.
Il y a une discussion sur la façon d'activer les listes d'assemblages ici: tiliser GCC pour produire un assemblage lisible?
Il peut être instructif d'examiner le code généré et de voir ce que fait réellement un compilateur.
Bien que toutes sortes de différences triviales avec le code puissent perturber le comportement du compilateur de manière à légèrement améliorer ou aggraver les performances, en principe, cela ne devrait pas faire de différence de performance si vous utilisez des variables temporaires comme celle-ci tant que la signification du programme n'est pas modifié. Un bon compilateur devrait générer le même code, ou un code comparable, de toute façon, à moins que vous ne construisiez intentionnellement avec l'optimisation désactivée afin d'obtenir un code machine aussi proche que possible de la source (par exemple à des fins de débogage).
Vous rencontrez le même problème que moi lorsque j'essaie d'apprendre ce que fait un compilateur - vous créez un programme trivial pour illustrer le problème et examinez la sortie Assembly du compilateur, seulement pour vous rendre compte que le compilateur a tout optimisé vous avez essayé de le faire disparaître. Vous pouvez même trouver une opération assez complexe dans main () réduite à essentiellement:
Push "%i"
Push 42
call printf
ret
Votre question initiale n'est pas "ce qui se passe avec int i = 5; int j = 10...
? "mais" les variables temporaires entraînent-elles généralement une pénalité d'exécution? "
La réponse est probablement non. Mais vous devez regarder la sortie de l'assembly pour votre code particulier et non trivial. Si votre CPU a beaucoup de registres, comme un ARM, alors i et j sont très probablement dans des registres, tout comme si ces registres stockaient directement la valeur de retour d'une fonction. Par exemple:
int i = func1();
int j = func2();
int result = i + j;
doit certainement être exactement le même code machine que:
int result = func1() + func2();
Je vous suggère d'utiliser des variables temporaires si elles facilitent la compréhension et la maintenance du code, et si vous essayez vraiment de resserrer une boucle, vous examinerez de toute façon la sortie de l'assembly pour comprendre comment affiner autant de performances que possible. Mais ne sacrifiez pas la lisibilité et la maintenabilité pendant quelques nanosecondes, si ce n'est pas nécessaire.