Je me demande simplement s’il y aurait une perte de vitesse ou d’efficacité si vous faisiez quelque chose comme ceci:
int i = 0; while(i < 100) { int var = 4; i++; }
qui déclare int var
cent fois. Il me semble qu’il y en aurait, mais je ne suis pas sûr. serait-il plus pratique / plus rapide de le faire à la place:
int i = 0; int var; while(i < 100) { var = 4; i++; }
ou sont-ils identiques, rapides et efficaces?
L’espace de stack pour les variables locales est généralement alloué dans la scope de la fonction. Donc, aucun ajustement de pointeur de stack ne se produit à l’intérieur de la boucle, il suffit d’atsortingbuer 4 à var
. Par conséquent, ces deux extraits de code ont la même surcharge.
Pour les types primitifs et les types de POD, cela ne fait aucune différence. Le compilateur allouera l’espace de stack pour la variable au début de la fonction et le désallouera lorsque la fonction retournera dans les deux cas.
Pour les types de classe non-POD qui ont des constructeurs non sortingviaux, cela fera une différence – dans ce cas, mettre la variable en dehors de la boucle appellera seulement le constructeur et le destructeur une fois et l’opérateur d’affectation chaque itération, loop appellera le constructeur et le destructeur pour chaque itération de la boucle. Selon ce que font les opérateurs de constructeur, de destructeur et d’affectation de la classe, cela peut être souhaitable ou non.
Ils sont tous les deux identiques, et voici comment vous pouvez le découvrir, en regardant ce que fait le compilateur (même sans optimisation):
Regardez ce que le compilateur (gcc 4.0) fait à vos exemples simples:
1.c:
main(){ int var; while(int i < 100) { var = 4; } }
gcc -S 1.c
1.s:
_main: pushl %ebp movl %esp, %ebp subl $24, %esp movl $0, -16(%ebp) jmp L2 L3: movl $4, -12(%ebp) L2: cmpl $99, -16(%ebp) jle L3 leave ret
2.c
main() { while(int i < 100) { int var = 4; } }
gcc -S 2.c
2.s:
_main: pushl %ebp movl %esp, %ebp subl $24, %esp movl $0, -16(%ebp) jmp L2 L3: movl $4, -12(%ebp) L2: cmpl $99, -16(%ebp) jle L3 leave ret
À partir de ceux-ci, vous pouvez voir deux choses: premièrement, le code est le même dans les deux.
Deuxièmement, le stockage pour var est alloué en dehors de la boucle:
subl $24, %esp
Et enfin la seule chose dans la boucle est la vérification de l'affectation et de la condition:
L3: movl $4, -12(%ebp) L2: cmpl $99, -16(%ebp) jle L3
Ce qui est à peu près aussi efficace que possible sans supprimer complètement la boucle.
De nos jours, il est préférable de le déclarer à l’intérieur de la boucle, sauf s’il s’agit d’une constante, car le compilateur sera en mesure de mieux optimiser le code (réduisant la scope des variables).
EDIT: Cette réponse est pour la plupart obsolète maintenant. Avec la montée en puissance des compilateurs post-classiques, les cas où le compilateur ne parvient pas à le comprendre deviennent rares. Je peux toujours les construire mais la plupart des gens classeraient la construction comme un mauvais code.
La plupart des compilateurs modernes vont optimiser cela pour vous. Cela étant dit, j’utiliserais votre premier exemple car je le trouve plus lisible.
Pour un type intégré, il n’y aura probablement pas de différence entre les deux styles (probablement jusqu’au code généré).
Cependant, si la variable est une classe avec un constructeur / destructeur non sortingvial, il pourrait bien y avoir une différence majeure dans le coût d’exécution. J’étendais généralement la variable à l’intérieur de la boucle (pour garder le champ d’application aussi petit que possible), mais si cela s’avère avoir un impact positif, je chercherais à déplacer la variable de classe hors de la scope de la boucle. Cependant, cela nécessite une parsing supplémentaire car la sémantique du chemin ode peut changer, donc cela ne peut être fait que si la sémantique le permet.
Une classe RAII peut avoir besoin de ce comportement. Par exemple, une classe qui gère la durée de vie de l’access aux fichiers peut devoir être créée et détruite à chaque itération de boucle pour gérer correctement l’access aux fichiers.
Supposons que vous ayez une classe LockMgr
qui acquiert une section critique lorsqu’elle est construite et la libère lorsqu’elle est détruite:
while (i< 100) { LockMgr lock( myCriticalSection); // acquires a critical section at start of // each loop iteration // do stuff... } // critical section is released at end of each loop iteration
est assez différent de:
LockMgr lock( myCriticalSection); while (i< 100) { // do stuff... }
Les deux boucles ont la même efficacité. Ils prendront tous deux une quantité de temps infinie 🙂 Il peut être judicieux d’incrémenter i dans les boucles.
J’ai déjà effectué des tests de performance, et à ma grande surprise, j’ai trouvé que le cas 1 était en fait plus rapide! Je suppose que c’est peut-être parce que la déclaration de la variable à l’intérieur de la boucle réduit sa scope, donc elle est libérée plus tôt. Cependant, c’était il y a longtemps, sur un compilateur très ancien. Je suis sûr que les compilateurs modernes optimisent les différences, mais il n’est pas difficile de garder la scope de la variable aussi courte que possible.
#include int main() { for(int i = 0; i < 10; i++) { int test; if(i == 0) test = 100; printf("%d\n", test); } }
Le code ci-dessus est toujours imprimé 100 fois 10, ce qui signifie que la variable locale à l'intérieur de la boucle n'est allouée qu'une fois pour chaque appel de fonction.
La seule façon de s’assurer est de les chronométrer. Mais la différence, s’il y en a une, sera microscopique, vous aurez donc besoin d’une grosse boucle de synchronisation.
Plus précisément, le premier est un meilleur style car il initialise la variable var, tandis que l’autre le laisse non initialisé. Ceci et la directive selon laquelle les variables doivent être aussi proches que possible de leur point d’utilisation signifient que la première forme devrait normalement être préférée.
Avec seulement deux variables, le compilateur sera probablement assigner un registre pour les deux. Ces registres existent quand même, donc cela ne prend pas de temps. Il y a 2 instructions d’écriture de registre et une instruction de lecture de registre dans les deux cas.
Je pense que la plupart des réponses manquent un point majeur à considérer qui est: “Est-ce clair” et de toute évidence par toute la discussion le fait est; non ce n’est pas Je suggère que dans la plupart des codes de boucle, l’efficacité est à peu près un problème (à moins que vous ne calculiez pour un lander), alors la seule question est de savoir ce qui semble plus sensible et lisible. la variable à l’avant et à l’extérieur de la boucle – cela le rend simplement plus clair. Alors les gens comme vous et moi ne prendraient même pas la peine de perdre du temps à vérifier en ligne pour voir si elle est valide ou non.
Ce n’est pas vrai, il y a des frais généraux mais sa négligence est possible.
Même si elles se retrouveront probablement au même endroit sur la stack, il l’atsortingbue toujours. Il assignera un emplacement de mémoire sur la stack pour cet int et le libérera à la fin de}. Pas dans le sens du tas en sens, cela va déplacer sp (pointeur de stack) de 1. Et dans votre cas, en considérant qu’il ne possède qu’une seule variable locale, il va simplement assimiler fp (pointeur de trame) et sp
La réponse courte serait: NE SOYEZ PAS SOIT QUE LA MANIÈRE FONCTIONNE LA MÊME.
Mais essayez de lire plus sur la façon dont la stack est organisée. Mon école de premier cycle a eu de très bonnes conférences à ce sujet Si vous voulez en savoir plus, consultez ici