web-dev-qa-db-fra.com

Existe-t-il un surcoût à la déclaration d'une variable dans une boucle? (C ++)

Je me demande simplement s'il y aurait une perte de vitesse ou d'efficacité si vous faisiez quelque chose comme ça:

int i = 0;
while(i < 100)
{
    int var = 4;
    i++;
}

qui déclare int var cent fois. Il me semble que ce serait le cas, mais je ne suis pas sûr. serait-il plus pratique/plus rapide de faire cela à la place:

int i = 0;
int var;
while(i < 100)
{
    var = 4;
    i++;
}

ou sont-ils les mêmes, en termes de rapidité et d'efficacité?

149
user98188

L'espace de pile pour les variables locales est généralement alloué dans l'étendue de la fonction. Donc, aucun ajustement du pointeur de pile ne se produit dans la boucle, il suffit d’attribuer 4 à var. Par conséquent, ces deux extraits ont la même surcharge.

189
laalto

Pour les types primitifs et les types POD, cela ne fait aucune différence. Le compilateur allouera l'espace de pile 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 ayant des constructeurs non triviaux, cela fera une différence - dans ce cas, le fait de placer la variable en dehors de la boucle n’appellera le constructeur et le destructeur qu’une seule fois, ainsi que l’opérateur d’affectation à chaque itération, loop appellera le constructeur et le destructeur pour chaque itération de la boucle. En fonction de ce que font le constructeur, le destructeur et l'opérateur d'affectation de la classe, cela peut être souhaitable ou non.

98
Adam Rosenfield

Ce sont les mêmes, et voici comment vous pouvez le savoir en regardant ce que fait le compilateur (même sans optimisation élevée):

Regardez ce que le compilateur (gcc 4.0) fait pour 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 identique dans les deux cas.

Deuxièmement, le stockage de var est alloué en dehors de la boucle:

         subl    $24, %esp

Et enfin, la seule chose dans la boucle est l'affectation et le contrôle de condition:

L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3

Ce qui est aussi efficace que possible sans supprimer entièrement la boucle.

66
Alex Brown

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éduction de la portée de la variable).

EDIT: Cette réponse est pour la plupart obsolète maintenant. Avec la montée des compilateurs post-classiques, les cas où le compilateur ne peut pas le comprendre deviennent rares. Je peux toujours les construire, mais la plupart des gens classeraient cette construction dans un code incorrect.

12
Joshua

La plupart des compilateurs modernes optimiseront cela pour vous. Cela étant dit, j'utiliserais votre premier exemple car je le trouve plus lisible.

10
Andrew Hare

Pour un type intégré, il n'y aura probablement pas de différence entre les 2 styles (probablement jusqu'au code généré).

Cependant, si la variable est une classe avec un constructeur/destructeur non trivial, il pourrait y avoir une différence majeure dans les coûts d'exécution. En règle générale, je modifiais la variable à l'intérieur de la boucle (pour qu'elle soit aussi petite que possible), mais si cela s'avérait avoir un impact optimal, je chercherais à déplacer la variable de classe en dehors de la portée de la boucle. Cependant, cela nécessite une analyse supplémentaire car la sémantique du chemin d'ode peut changer, ce qui ne peut donc ê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'accès aux fichiers peut avoir besoin d'être créée et détruite à chaque itération de boucle pour gérer correctement l'accès 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...

}
8
Michael Burr

Les deux boucles ont la même efficacité. Ils prendront tous les deux un temps infini :) Cela peut être une bonne idée d’incrémenter i à l’intérieur des boucles.

6
Larry Watanabe

Une fois, j'ai effectué des tests de performance et, à ma grande surprise, j'ai constaté que le cas 1 était en réalité 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 portée, de sorte qu'elle est libérée plus tôt. Cependant, c'était il y a longtemps, sur un très vieux compilateur. Je suis sûr que les compilateurs modernes optimisent mieux les différences, mais cela ne fait pas de mal de garder votre portée variable aussi courte que possible.

2
user3864776
#include <stdio.h>
int main()
{
    for(int i = 0; i < 10; i++)
    {
        int test;
        if(i == 0)
            test = 100;
        printf("%d\n", test);
    }
}

Le code ci-dessus affiche toujours 100 fois 10, ce qui signifie que la variable locale dans la boucle n’est attribuée qu’une fois par appel de fonction.

2
Byeonggon Lee

Le seul moyen d'être sûr est de les chronométrer. Mais la différence, s’il en existe une, sera microscopique, vous aurez donc besoin d’une puissante boucle de synchronisation.

Plus précisément, le premier est meilleur style car il initialise la variable var, tandis que l’autre la laisse non initialisée. Ceci et la directive selon laquelle il faut définir les variables aussi près que possible de leur point d'utilisation signifie que la première forme doit normalement être préférée.

0
anon