Pergunta

Eu sempre me perguntei se, em geral, declarar uma variável descartável antes de um loop, em oposição a várias vezes dentro do loop, faz alguma diferença (desempenho)? A (bastante inútil) exemplo em Java:

a) declaração antes de loop:

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

b) declaração (repetidamente) loop interno:

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

Qual é melhor, a ou b ?

Eu suspeito que repetiu declaração da variável (exemplo b ) cria mais sobrecarga em teoria , mas isso compiladores são suficientes inteligente para que ele não importa. Exemplo b tem a vantagem de ser mais compacto e limitar o âmbito da variável para onde ele é utilizado. Ainda assim, eu tendem a código de acordo exemplo a .

Editar:. Estou especialmente interessado no caso Java

Foi útil?

Solução

O que é melhor, a ou b ?

A partir de uma perspectiva de desempenho, você tem que medir isso. (E, na minha opinião, se você pode medir a diferença, o compilador não é muito bom).

Do ponto de vista de manutenção, b é melhor. Declarar e variáveis ??inicializar no mesmo lugar, no âmbito mais estreito possível. Não deixe um buraco entre a declaração ea inicialização, e fazer namespaces Não polua você não precisa.

Outras dicas

Bem, eu corri seus exemplos A e B 20 vezes cada um, looping 100 milhões de vezes (JVM - 1.5.0).

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

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

Para minha surpresa B foi um pouco mais rápido. Tão rápido quanto os computadores são agora é difícil dizer se você pudesse medir com precisão este. Gostaria de codificá-lo a um caminho tão bem, mas eu diria que realmente não importa.

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

Exemplo:

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

Output:

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 capturar a mesma variável outer, mas cada um tem sua própria variável inner separado.

O seguinte é 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);
}

Isto é o que eu recebo de .NET Reflector quando CIL é processado de volta em 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);
}

Assim, ambos olhar exatamente o mesmo após a compilação. Em linguagens de código gerenciado é convertido em CL / código de byte e em tempo de execução é convertida em linguagem de máquina. Assim, em linguagem de máquina um duplo não pode mesmo ser criado na pilha. Pode ser apenas um registo como código refletir que é uma variável temporária para a função WriteLine. Existem regras integrais de otimização conjunto apenas para loops. Assim, o indivíduo médio não deve se preocupar com isso, especialmente em idiomas gerenciados. Há casos em que você a otimizar pode gerenciar código, por exemplo, se você tem que concatenar um grande número de cordas usando apenas string a; a+=anotherstring[i] vs usando StringBuilder. Não é muito grande diferença no desempenho entre ambos. Há uma série de casos em que o compilador não pode otimizar o seu código, porque ele não consegue descobrir o que se pretende em um escopo maior. Mas pode muito bem otimizar coisas básicas para você.

Esta é uma pegadinha no VB.NET. O resultado Visual Basic não vai 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...

Isto irá imprimir 0 pela primeira vez (variáveis ??Visual Basic têm valores padrão quando declarou!) Mas i cada tempo depois disso.

Se você adicionar um = 0, porém, você recebe o que você 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 gcc - 5.2.0. E então eu desmontou o main () destes dois códigos e isso é o resultado:

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 

Quais exaclty o mesmo resultado asm. não é uma prova de que os dois códigos de produzir a mesma coisa?

Eu sempre utilizar A (em vez de depender do compilador) e também pode reescrever a:

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

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

É a linguagem dependente -. IIRC C # otimiza isso, então não há qualquer diferença, mas JavaScript (por exemplo) vai fazer toda a alocação de memória shebang cada vez

Na minha opinião, b é a melhor estrutura. Em um, o último valor de varas intermediateResult ao redor após o seu ciclo está terminado.

Edit: Isso não faz muita diferença com tipos de valor, mas os tipos de referência pode ser um pouco pesado. Pessoalmente, eu como variáveis ??a serem desreferenciado o mais rápido possível para limpeza, e b faz isso para você,

Eu suspeito que alguns compiladores podem otimizar tanto a ser o mesmo código, mas certamente não todos. Então eu diria que você é melhor fora com o primeiro. A única razão para este último é, se você quiser garantir que a variável declarada é utilizada única dentro de seu loop.

Como regra geral, eu declaro meus variáveis ??no escopo mais interna possível. Então, se você não está usando intermediateResult fora do circuito, então eu iria com B.

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

Eu prefiro o segundo (! E tentar convencer o meu colega de trabalho ;-)), depois de ter lido que:

  • Reduz escopo de variáveis ??para onde eles são necessários, o que é uma coisa boa.
  • Java otimiza o suficiente para fazer nenhuma diferença significativa no desempenho. IIRC, talvez a segunda forma é ainda mais rápido.

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

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

Tendo em conta que eles são basicamente os mesmos: Note-se que a versão b torna muito mais evidente para os leitores que a variável não é, e não pode, ser usado após o loop. Além disso, versão b é muito mais facilmente reformulado. É mais difícil de extrair o corpo do laço em seu próprio método na versão a. Além disso, versão B garante que não há nenhum efeito colateral a tal refatoração.

Assim, uma versão irrita-me para nenhum fim, porque não há nenhum benefício para ele e isso torna muito mais difícil de raciocinar sobre o código ...

Bem, você sempre pode fazer um espaço para que:

{ //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ó declarar a variável uma vez, e ele vai morrer quando sair do loop.

Eu sempre pensei que, se você declarar suas variáveis ??dentro de seu loop, então você está desperdiçando memória. Se você tem algo parecido com isto:

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

Em seguida, não só a necessidade objeto a ser criado para cada iteração, mas é preciso que haja uma nova referência atribuída para cada objeto. Parece que se o coletor de lixo é lenta, então você vai ter um monte de balançando referências que precisam ser limpos.

No entanto, se você tem o seguinte:

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

Em seguida, você está criando apenas uma única referência e atribuir um novo objeto a ele cada vez. Claro, isso pode demorar um pouco mais para que ele vá fora do escopo, mas depois há apenas uma referência pendendo para lidar com eles.

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

A minha prática é o seguinte:

  • Se tipo de variável é simples (int, double, ...) Eu prefiro variante b (no interior).
    Motivo: reduzindo âmbito da variável.

  • Se tipo de variável não é simples (algum tipo de class ou struct) Eu prefiro variante a (fora).
    Motivo:. reduzindo o número de chamadas ctor-dtor

A partir de uma perspectiva de desempenho, fora é (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 executada ambas as funções 1 bilhão de vezes cada. fora () levou 65 milissegundos. dentro () levou 1,5 segundos.

A) é uma aposta segura que B) ......... Imagine se você está inicializando estrutura em malha em vez de 'int' ou 'float' e depois?

como

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 obrigado a enfrentar problemas com vazamentos de memória !. Por isso eu acredito 'A' é aposta mais segura, enquanto 'B' é vulnerável à acumulação de memória esp trabalhando fonte próxima libraries.You pode verificar usinng Ferramenta 'Valgrind' no Linux especificamente sub ferramenta 'Helgrind'.

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

Existe alguma razão para que a variável que precisa ser global?

Não faz sentido apenas para 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 só precisa ser declarado localmente dentro de um método, eu inicializá-lo nesse método, por isso é claro que a variável é exclusivamente relevante para esse método. Tenha cuidado para não chamar esta variável fora do método em que é inicializado, se você escolher a última opção -. Seu código não vai saber o que você está falando e se reportará um erro

Além disso, como uma nota lateral, não duplicar nomes de variáveis ??locais entre diferentes métodos, mesmo que seus fins são quase idênticas; ele só fica confuso.

Eu testei para JS com Nó 4.0.0 Se alguém estiver interessado. Declarando fora do loop resultou numa melhoria do desempenho .5 ms ~, em média, mais de 1000 ensaios com 100 milhões de iterações em cada experiência de ansa. Então eu vou dizer vá em frente e escrevê-lo da forma mais legível / passível de manutenção que é B, imo. Eu colocaria meu código em um violino, mas eu usei o módulo Node desempenho de 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), desta forma declarada uma vez tempo, tanto variável, e não para cada ciclo. 2) a atribuição-lo de fatser thean tudo outra opção. 3) Portanto, a regra bestpractice é qualquer declaração fora da iteração para.

tentou a mesma coisa em Go, e comparou a saída do compilador usando go tool compile -S com go 1.9.4

Zero diferença, de acordo com a saída do integrador.

Eu tinha essa mesma pergunta por um longo tempo. Então, eu testei uma peça ainda mais simples de código.

Conclusão:. Para Nesses casos não é NÃO diferença de desempenho

caso circuito Fora

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

Dentro caso de loop

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

Eu verifiquei o arquivo compilado em decompiler do IntelliJ e para ambos os casos, eu tenho o mesma Test.class

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

I código também desmontado, tanto para o caso, utilizando o método apresentado neste resposta . Vou mostrar apenas as partes relevantes para a resposta

caso circuito Fora

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

Dentro caso de loop

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

Se você prestar atenção, apenas o Slot atribuído a i e intermediateResult em LocalVariableTable é trocado como um produto da sua ordem de aparecimento. A mesma diferença de ranhura é reflectida em outras linhas de código.

  • Nenhuma operação adicional está sendo executada
  • intermediateResult ainda é uma variável local em ambos os casos, portanto, não há tempo de acesso diferença.

bônus

Os compiladores fazer uma tonelada de otimização, dê uma olhada no que acontece neste caso.

Zero caso de trabalho

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

trabalho Zero decompiled

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

Mesmo se eu sei que meu compilador é inteligente o suficiente, eu não vou gostar de contar com ele, e usará o a) variante.

A b) variante faz sentido para mim se você precisa desesperadamente fazer o intermediateResult não disponível depois que o corpo do laço. Mas eu não posso imaginar tal situação desesperada, de qualquer maneira ....

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top