Différence entre les variables déclarantes avant ou en boucle?

je me suis toujours demandé si, en général, déclarer une variable "jeter" devant une boucle, par opposition à répéter à l'intérieur de la boucle, fait une différence (de performance)? Un (tout à fait inutile) exemple en Java:

a) déclaration avant boucle:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

b) déclaration (répétée) boucle intérieure:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

lequel est le meilleur, un ou b ?

je soupçonne que la déclaration répétée de variable (exemple b ) crée plus de frais généraux en théorie , mais que les compilateurs sont assez intelligents pour qu'il n'importe pas. Exemple b a l'avantage d'être plus compact et de limiter la portée de la variable à l'endroit où il est utilisé. Pourtant, j'ai tendance à coder selon l'exemple a .

Edit: je suis surtout intéressé par les Java cas.

292
demandé sur GedankenNebel 2009-01-02 19:06:40
la source

24 ответов

Quel est le meilleur, a ou b ?

du point de vue de la performance, il faudrait le mesurer. (Et à mon avis, si vous pouvez mesurer une différence, le compilateur n'est pas très bon).

du point de vue de la maintenance, b est mieux. Déclarez et initialisez les variables au même endroit, dans la portée la plus étroite possible. Ne pas laisser un trou béant entre la déclaration et l'initialisation, et ne polluez pas les namespaces dont vous n'avez pas besoin.

240
répondu Daniel Earwicker 2017-05-25 10:52:41
la source

J'ai fait 20 fois vos exemples A et B, 100 millions de fois en boucle.(JVM - 1.5.0)

A: Durée moyenne d'exécution:.074 sec

B: durée moyenne d'exécution:.067 sec 151910920"

à ma grande surprise B était un peu plus rapide. Aussi vite que les ordinateurs sont maintenant il est difficile de dire si vous pouvez mesurer avec précision. Je voudrais le code de la manière, mais je dirais qu'il n'a pas vraiment d'importance.

203
répondu Mark 2009-01-02 19:25:15
la source

Cela dépend de la langue et l'utilisation exacte. Par exemple, en C# 1 cela n'a fait aucune différence. En C# 2, si la variable locale est capturée par une méthode anonyme (ou expression lambda en C# 3), cela peut faire une différence très significative.

exemple:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

sortie:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

la différence est que toutes les actions capturer la même variable outer , mais chacun a sa propre séparée inner variable.

65
répondu Jon Skeet 2009-01-02 19:16:53
la source

ce qui suit est ce que j'ai écrit et compilé .NET.

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

C'est ce que j'obtiens de . réflecteur de réseau quand CIL est rendu de nouveau dans le code.

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

Donc, à la fois exactement le même après la compilation. Dans les langues gérées, le code est converti en code CL / byte et au moment de l'exécution, il est converti en langage machine. Ainsi, en langage machine, un double peut ne pas être créé sur la pile. Il se peut qu'il s'agisse simplement d'un registre car le code indique qu'il s'agit d'une variable temporaire pour la fonction WriteLine . Il y a toute une série de règles d'optimisation pour les boucles. Donc, le gars moyen ne devrait pas s'inquiéter à ce sujet, surtout dans les langues gérées. Il y a des cas où vous pouvez optimiser la gestion du code, par exemple, si vous devez concaténer un grand nombre de chaînes en utilisant juste string a; a+=anotherstring[i] vs En utilisant StringBuilder . Il y a une grande différence de performance entre les deux. Il y a beaucoup de ces cas où le compilateur ne peut pas optimiser votre code, parce qu'il ne peut pas comprendre ce qui est prévu dans une plus grande portée. Mais il peut à peu près optimiser les choses de base pour vous.

35
répondu particle 2015-09-08 22:36:50
la source

C'est un gotcha en VB.NET. Le résultat Visual Basic ne réinitialisera pas la variable dans cet exemple:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

cela affichera 0 la première fois (les variables Visual Basic ont des valeurs par défaut lorsqu'elles sont déclarées!), mais i chaque fois d'après.

si vous ajoutez un = 0 , cependant, vous obtenez ce que vous pourriez attendre:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...
24
répondu Michael Haren 2015-09-08 22:30:38
la source

j'ai fait un test simple:

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

vs

for (int i = 0; i < 10; i++) {
    int b = i;
}

j'ai compilé ces codes avec gcc - 5.2.0. Et puis j'ai démonté le principal () de ces deux codes et c'est le résultat:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

vs

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

qui sont exactement le même résultat que moi. n'est-ce pas une preuve que les deux codes produisent la même chose?

13
répondu UserX 2015-10-10 17:28:51
la source

j'utiliserais toujours un (plutôt que de compter sur le compilateur) et je pourrais aussi réécrire en:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

cela restreint toujours intermediateResult à la portée de la boucle, mais ne fait pas de retouche à chaque itération.

11
répondu Triptych 2009-01-02 19:18:13
la source

il est dépendant de la langue - IIRC c# optimise cela, donc il n'y a pas de différence, mais JavaScript (par exemple) fera l'attribution de la mémoire entière shebang à chaque fois.

11
répondu annakata 2015-09-08 22:28:13
la source

À mon avis, b est la meilleure structure. En a, la dernière valeur de intermediateResult reste après que votre boucle est terminée.

Edit: Cela ne fait pas beaucoup de différence avec les types de valeur, mais les types de référence peuvent être un peu lourds. Personnellement, j'aime que les variables soient déréférencées dès que possible pour le nettoyage, et b le fait pour vous,

6
répondu Powerlord 2009-01-02 20:21:43
la source

je soupçonne quelques compilateurs pourraient optimiser les deux pour être le même code, mais certainement pas tous. Donc je dirais que tu es mieux avec le premier. La seule raison pour cette dernière est si vous voulez vous assurer que la variable déclarée est utilisée seulement dans votre boucle.

5
répondu Stew S 2009-01-02 19:14:05
la source

en règle générale, je déclare mes variables dans la portée intérieure la plus possible. Donc, si vous n'utilisez pas intermediateResult en dehors de la boucle, alors je dirais B.

5
répondu Chris 2009-01-02 19:15:15
la source

un collègue préfère la première forme, disant qu'il s'agit d'une optimisation, préférant réutiliser une déclaration.

je préfère la seconde (et essayer de convaincre mon collègue! ;- )), ayant lu que:

  • il réduit la portée des variables là où elles sont nécessaires, ce qui est une bonne chose.
  • Java optimise suffisamment pour ne pas faire de différence significative dans les performances. IIRC, peut-être que la seconde forme est encore plus rapide.

de toute façon, il tombe dans la catégorie de l'optimisation prématurée qui dépendent de la qualité du compilateur et/ou JVM.

5
répondu PhiLho 2009-01-02 20:41:30
la source

il y a une différence dans C# si vous utilisez la variable dans une lambda, etc. Mais en général, le compilateur va faire la même chose, en supposant que la variable n'est utilisée dans la boucle.

étant donné qu'ils sont essentiellement les mêmes: notez que la version b rend beaucoup plus évident pour les lecteurs que la variable n'est pas, et ne peut pas être utilisé après la boucle. De plus, version b est beaucoup plus facilement refactorisé. Il est plus difficile d'extraire l' le corps de boucle dans sa propre méthode dans la version a. de plus, la version b vous assure qu'il n'y a aucun effet secondaire à un tel remaniement.

par conséquent, la version a m'ennuie sans fin, parce qu'il n'y a aucun avantage à cela et cela rend beaucoup plus difficile de raisonner au sujet du code...

5
répondu Mark Sowul 2012-11-08 01:15:13
la source

Eh bien, vous pourriez toujours faire une portée pour cela:

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

de cette façon, vous ne déclarez la variable qu'une seule fois, et elle mourra lorsque vous quitterez la boucle.

5
répondu Marcelo Faísca 2015-09-08 22:37:44
la source

j'ai toujours pensé que si vous déclarez vos variables à l'intérieur de votre boucle, alors vous gaspillez de la mémoire. Si vous avez quelque chose comme ceci:

for(;;) {
  Object o = new Object();
}

Alors, non seulement, l'objet doit être créé pour chaque itération, mais il doit être une nouvelle référence attribué pour chaque objet. Il semble que si le collecteur d'ordures est lent alors vous aurez un tas de références pendantes qui doivent être nettoyées.

cependant, si vous avez ceci:

Object o;
for(;;) {
  o = new Object();
}

alors vous ne créez qu'une seule référence et vous lui assignez un nouvel objet à chaque fois. Bien sûr, il faudra peut-être un peu plus de temps pour que ça sorte, mais il n'y a qu'une seule référence pendante à traiter.

4
répondu R. Carr 2011-12-28 02:39:19
la source

je pense que ça dépend du compilateur et il est difficile de donner une réponse générale.

3
répondu SquidScareMe 2009-01-02 19:08:53
la source

ma pratique est la suivante:

  • si le type de variable est simple (int, double, ...) je préfère la variante b (à l'intérieur).

    : la réduction de portée de variable.

  • si le type de variable n'est pas simple (une sorte de class ou struct ) je préfère variante un (à l'extérieur).

    : en réduisant le nombre de ctor-dtor appels.

3
répondu fat 2014-02-05 10:03:24
la source

du point de vue de la performance, l'extérieur est (beaucoup) meilleur.

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

j'ai exécuté les deux fonctions 1 milliard de fois chacune. outside () a pris 65 millisecondes. inside () a pris 1,5 seconde.

1
répondu Alex 2015-04-20 15:29:41
la source

) est fort à parier que B).........Imaginez si vous initialisez la structure en boucle plutôt que' int 'ou' float ' alors quoi?

comme

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

vous êtes certainement destinés à faire face à des problèmes avec des fuites de mémoire!. Par conséquent, je crois que " A "est plus sûr bet tandis que" B " est vulnérable à l'accumulation de mémoire esp travaillant à proximité des bibliothèques source.Vous pouvez vérifier L'utilisation de L'outil' Valgrind 'sous Linux spécifiquement sous l'outil'Helgrind'.

0
répondu enthusiasticgeek 2011-12-28 02:39:42
la source

c'est une question intéressante. D'après mon expérience, il y a une question ultime à considérer lorsque vous débattez de cette question pour un code:

y a-t-il une raison pour laquelle la variable devrait être globale?

il est logique de déclarer la variable qu'une seule fois, globalement, par opposition à plusieurs fois localement, parce qu'il est préférable d'organiser le code et nécessite moins de lignes de code. Toutefois, si elle ne doit être déclaré localement au sein d'une méthode, je l'initialiserais dans cette méthode afin qu'il soit clair que la variable est exclusivement pertinente à cette méthode. Attention à ne pas appeler cette variable en dehors de la méthode dans laquelle elle est initialisée si vous choisissez la dernière option--votre code ne saura pas de quoi vous parlez et signalera une erreur.

aussi, comme note latérale, ne dupliquez pas les noms de variables locales entre les différentes méthodes, même si leurs buts sont presque identiques; il suffit devient source de confusion.

0
répondu Joshua Siktar 2015-03-06 06:38:12
la source

j'ai testé pour JS avec Node 4.0.0 si quelqu'un est intéressé. La déclaration à l'extérieur de la boucle a donné lieu à un ~.5 ms amélioration de la performance en moyenne plus de 1000 essais avec 100 millions d'itérations de boucle par essai. Donc je vais vous dire de l'écrire de la façon la plus lisible / maintenable qui soit, B, imo. Je mettrais mon code dans un violon, mais j'ai utilisé le module Performance-now Node. Voici le code:

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)
0
répondu user137717 2016-04-04 23:32:19
la source

c'est la meilleure forme

double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}

1) de cette façon, déclaré une fois à la fois variable, et pas chacun pour le cycle. 2) le devoir c'est fatser thean toute autre option. 3) ainsi la règle de bestpractice est n'importe quelle déclaration en dehors de l'itération pour.

0
répondu luka 2016-07-13 14:06:52
la source

a essayé la même chose dans Go, et a comparé la sortie du compilateur en utilisant go tool compile -S avec go 1.9.4

différence zéro, selon la sortie de l'assembleur.

0
répondu SteveOC 64 2018-02-19 08:25:25
la source

Même si je sais que mon compilateur est assez intelligent, je n'aime pas compter sur elle, et utilise une variante.

la variante b) n'a de sens pour moi que si vous avez désespérément besoin de rendre le intermediateResult indisponible après le corps de boucle. Mais je ne peux pas imaginer une telle situation désespérée, de toute façon....

EDIT: Jon Skeet a fait un très bon point, montrant que la déclaration de la variable à l'intérieur d'une boucle peut faire une réelle différence sémantique.

-1
répondu Abgan 2009-01-02 19:23:06
la source