Diferença entre declarar variables ​​antes ou em loop?

Eu sempre me perguntei se, em geral, declarar uma variável descartável antes de um loop, ao contrário de repetidamente dentro do loop, faz alguma diferença (performance)? Um exemplo (sem sentido) em Java:

a) declaração antes do loop:

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

b) declaração (repetidamente) dentro do loop:

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

Qual deles é melhor, a ou b ?

Eu suspeito que a declaração de variável repetida (exemplo b ) crie mais sobrecarga em teoria , mas que os compiladores são inteligentes o suficiente para que isso não importe. O exemplo b tem a vantagem de ser mais compacto e limitar o escopo da variável para onde é usado. Ainda assim, tendem a codificar de acordo com o exemplo a .

Edit: Estou especialmente interessado no caso de Java.

Qual é melhor, a ou b ?

Do ponto de vista do desempenho, você teria que medi-lo. (E na minha opinião, se você pode medir uma diferença, o compilador não é muito bom).

De uma perspectiva de manutenção, b é melhor. Declarar e inicializar variables ​​no mesmo local, no menor escopo possível. Não deixe um buraco entre a declaração e a boot, e não polua namespaces que você não precisa.

Bom eu corri seus exemplos A e B 20 vezes cada, dando um loop 100 milhões de vezes (JVM – 1.5.0)

A: tempo médio de execução: 0,074 s

B: tempo médio de execução: 0,067 s

Para minha surpresa, B foi ligeiramente mais rápido. Tão rápido quanto os computadores agora são difíceis de dizer se você pudesse medir com precisão isso. Eu codificaria o modo A também, mas eu diria que isso realmente não importa.

Depende da linguagem e do uso exato. Por exemplo, em C # 1 não fazia diferença. Em C # 2, se a variável local é capturada por um método anônimo (ou expressão lambda em C # 3), isso pode fazer uma diferença muito significativa.

Exemplo:

 using System; using System.Collections.Generic; class Test { static void Main() { List actions = new List(); 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(); } } } 

Saída:

 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 

A diferença é que todas as ações capturam a mesma variável outer , mas cada uma tem sua própria variável inner separada.

O que se segue é o que eu escrevi e compilado no .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); } 

Isso é o que eu recebo do .NET Reflector quando o CIL é renderizado de volta no código.

 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); } 

Então, ambos parecem exatamente iguais após a compilation. Em idiomas gerenciados, o código é convertido em código de CL / byte e, no momento da execução, é convertido em linguagem de máquina. Portanto, na linguagem de máquina, um duplo pode nem ser criado na pilha. Pode ser apenas um registro enquanto o código reflete que é uma variável temporária para a function WriteLine . Há um todo conjunto de regras de otimização apenas for loops. Então, o cara comum não deveria se preocupar com isso, especialmente em idiomas gerenciados. Há casos em que você pode otimizar o código de gerenciamento, por exemplo, se tiver que concatenar um grande número de strings usando apenas a string a; a+=anotherstring[i] string a; a+=anotherstring[i] vs usando StringBuilder . Há uma grande diferença no desempenho entre os dois. Existem muitos casos em que o compilador não pode otimizar seu código, porque não consegue descobrir o que se pretende em um escopo maior. Mas pode otimizar bastante as coisas básicas para você.

Isso é uma pegadinha no VB.NET. O resultado do Visual Basic não reinicializará a variável neste exemplo:

 For i as Integer = 1 to 100 Dim j as Integer Console.WriteLine(j) j = i Next ' Output: 0 1 2 3 4... 

Isso imprimirá 0 na primeira vez (as variables ​​do Visual Basic têm valores padrão quando declaradas!), Mas i cada vez depois disso.

Se você adicionar um = 0 , no entanto, você obtém o que poderia esperar:

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

Eu fiz um teste simples:

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

vs

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

Eu compilei estes códigos com o gcc - 5.2.0. E então eu desmontei o main () desses dois códigos e esse é o resultado:

1º:

  0x00000000004004b6 <+0>: push rbp 0x00000000004004b7 <+1>: mov rbp,rsp 0x00000000004004ba <+4>: mov DWORD PTR [rbp-0x4],0x0 0x00000000004004c1 <+11>: jmp 0x4004cd  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  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  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  0x00000000004004d3 <+29>: mov eax,0x0 0x00000000004004d8 <+34>: pop rbp 0x00000000004004d9 <+35>: ret 

Que são exatamente o mesmo resultado. não é uma prova de que os dois códigos produzem a mesma coisa?

Eu sempre usaria A (ao invés de depender do compilador) e também poderia rewrite para:

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

Isso ainda restringe intermediateResult ao escopo do loop, mas não redeclare durante cada iteração.

É dependente do idioma – o IIRC C # otimiza isso, então não há nenhuma diferença, mas o JavaScript (por exemplo) fará toda a alocação de memory toda hora.

Na minha opinião, b é a melhor estrutura. Em um, o último valor de intermediateResult permanece após o término do loop.

Edit: Isso não faz muita diferença com os tipos de valor, mas os tipos de referência podem ser um pouco pesados. Pessoalmente, eu gosto que as variables ​​sejam desreferenciadas o mais rápido possível para a limpeza, e b faz isso para você,

Eu suspeito que alguns compiladores poderiam otimizar ambos para serem o mesmo código, mas certamente não todos. Então eu diria que você está melhor com o primeiro. A única razão para este último é se você quiser garantir que a variável declarada seja usada apenas dentro do seu loop.

Como regra geral, declaro minhas variables ​​no escopo mais interno possível. Então, se você não estiver usando intermediaResult fora do loop, então eu vou com B.

Um colega de trabalho prefere a primeira forma, dizendo que é uma otimização, preferindo reutilizar uma declaração.

Eu prefiro o segundo (e tento persuadir meu colega de trabalho! ;-)), depois de ler isso:

  • Ele reduz o escopo das variables ​​para onde elas são necessárias, o que é uma coisa boa.
  • Java otimiza o suficiente para não fazer diferença significativa no desempenho. IIRC, talvez a segunda forma seja ainda mais rápida.

De qualquer forma, cai na categoria de otimização prematura que depende da qualidade do compilador e / ou da JVM.

Há uma diferença em C # se você estiver usando a variável em um lambda, etc. Mas, em geral, o compilador fará basicamente a mesma coisa, assumindo que a variável é usada apenas dentro do loop.

Dado que eles são basicamente os mesmos: Note que a versão b torna muito mais óbvio para os leitores que a variável não é, e não pode, ser usada após o loop. Além disso, a versão b é muito mais facilmente refatorada. É mais difícil extrair o corpo do loop em seu próprio método na versão a. Além disso, a versão b garante que não há efeitos colaterais para tal refatoração.

Assim, a versão a me irrita sem fim, porque não há nenhum benefício para ela e torna muito mais difícil raciocinar sobre o código …

Bem, você sempre pode fazer um escopo para isso:

 { //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); } } 

Desta forma, você só declara a variável uma vez e ela morrerá quando você deixar o loop.

Eu sempre pensei que se você declarasse suas variables ​​dentro do seu loop, então você estaria perdendo memory. Se você tem algo assim:

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

Então, não apenas o object precisa ser criado para cada iteração, mas também precisa haver uma nova referência alocada para cada object. Parece que, se o coletor de lixo for lento, você terá um monte de referências pendentes que precisam ser limpas.

No entanto, se você tiver isso:

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

Então você está apenas criando uma única referência e atribuindo um novo object a ela toda vez. Claro, pode demorar um pouco mais para sair do escopo, mas há apenas uma referência pendente para lidar.

Eu acho que depende do compilador e é difícil dar uma resposta geral.

Minha prática é a seguinte:

  • se tipo de variável é simples (int, double, …) eu prefiro variante b (dentro).
    Razão: redução do escopo da variável.

  • se tipo de variável não é simples (algum tipo de class ou struct ) eu prefiro variante a (fora).
    Razão: redução do número de chamadas do ctor-dtor.

Do ponto de vista do desempenho, o exterior é (muito) melhor.

 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; } } 

Eu executei ambas as funções 1 bilhão de vezes cada. outside () levou 65 milissegundos. dentro () levou 1,5 segundos.

A) é uma aposta segura do que B) ……… Imagine se você está inicializando a estrutura em loop em vez de ‘int’ ou ‘float’ e depois?

gostar

 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? } 

Você certamente está fadado a enfrentar problemas com vazamentos de memory! Por isso, acredito que ‘A’ é uma aposta mais segura, enquanto ‘B’ é vulnerável ao acúmulo de memory, esp, trabalhando bibliotecas de fonts próximas.Você pode verificar usando a ferramenta ‘Valgrind’ no Linux, especificamente sub ferramenta ‘Helgrind’.

É uma questão interessante. Da minha experiência, há uma questão fundamental a considerar quando você debate este assunto para um código:

Existe alguma razão pela qual a variável precisaria ser global?

Faz sentido apenas declarar a variável uma vez, globalmente, ao contrário de muitas vezes localmente, porque é melhor para organizar o código e requer menos linhas de código. No entanto, se ele precisar ser declarado localmente em um método, eu o inicializaria nesse método, portanto, é claro que a variável é exclusivamente relevante para esse método. Tenha cuidado para não chamar essa variável fora do método no qual ela é inicializada se você escolher a última opção – seu código não saberá do que está falando e informará um erro.

Além disso, como uma nota lateral, não duplique nomes de variables ​​locais entre methods diferentes, mesmo que seus objectives sejam quase idênticos; só fica confuso.

Eu testei para JS com o Node 4.0.0 se alguém estiver interessado. Declarar fora do loop resultou em uma melhoria de desempenho de ~ 0,5 ms, em média, em mais de 1000 tentativas, com 100 milhões de iterações de loop por teste. Então, eu vou dizer vá em frente e escreva da maneira mais legível / sustentável que é B, imo. Eu colocaria meu código em um violino, mas usei o módulo Node de desempenho agora. Aqui está o código:

 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) 

esta é a melhor forma

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

1) deste modo declarado uma vez a variável tempo, e não cada um para o ciclo. 2) a tarefa é mais simples que todas as outras opções. 3) Portanto, a regra de melhor prática é qualquer declaração fora da iteração.

Tentei a mesma coisa no Go, e comparei a saída do compilador usando a go tool compile -S with go 1.9.4

Diferença zero, conforme a saída do montador.

Mesmo que eu saiba que meu compilador é inteligente o suficiente, eu não gostaria de confiar nele, e usarei a variante a).

A variante b) só faz sentido para mim se você precisar tornar o intermediárioResult indisponível após o corpo do loop. Mas eu não posso imaginar uma situação tão desesperada, de qualquer maneira ….

EDIT: Jon Skeet fez um bom ponto, mostrando que a declaração de variables ​​dentro de um loop pode fazer uma diferença semântica real.