Declarando uma variável dentro ou fora de um loop foreach: o que é mais rápido / melhor?

92

Qual destes é o mais rápido / melhor?

Este:

List<User> list = new List<User>();
User u;

foreach (string s in l)
{
    u = new User();
    u.Name = s;
    list.Add(u);
}

Ou este:

List<User> list = new List<User>();

foreach (string s in l)
{
    User u = new User();
    u.Name = s;
    list.Add(u);
}

Minhas habilidades de desenvolvimento de novato me dizem que o primeiro é melhor, mas um amigo meu me disse que estou errado, mas não poderia me dar um bom motivo para o segundo ser melhor.

Existe alguma diferença no desempenho?

Marcus
fonte

Respostas:

112

Em termos de desempenho, os dois exemplos são compilados no mesmo IL, então não há diferença.

O segundo é melhor, porque expressa mais claramente sua intenção se ufor usado apenas dentro do loop.

dtb
fonte
10
Note que não é uma diferença se a variável é capturado por uma expressão lambda ou delegado anônimo; veja Armadilha de Variável Externa .
dtb
Você pode explicar por que ambos são compilados no mesmo IL? Tenho certeza de que o C # não eleva as declarações de variáveis ​​para o topo da função como o javascript faz.
elegante
4
@styfle aqui está a resposta à sua pergunta.
David Sherret de
Os links do Stack Overflow a seguir fornecem respostas mais detalhadas: 1) Jon Hanna e 2) StriplingWarrior
user3613932
14

Em qualquer caso, a melhor maneira seria usar um construtor que receba um Nome ... ou, caso contrário, explorar a notação de chaves:

foreach (string s in l)
{
    list.Add(new User(s));
}

ou

foreach (string s in l)
{
    list.Add(new User() { Name = s });
}

ou melhor ainda, LINQ:

var list = l.Select( s => new User { Name = s});

Agora, enquanto seu primeiro exemplo pode, em alguns casos, ser inaceitavelmente mais rápido, o segundo é melhor porque é mais legível, e o compilador pode descartar a variável (e omiti-la completamente), já que ela não é usada fora do foreachescopo do.

Tordek
fonte
6
Comentário necrofílico do dia: "ou melhor ainda, LINQ". Claro que é uma linha de código e isso nos faz sentir bem, como desenvolvedores. Mas a versão de quatro linhas é MUITO mais compreensível e, portanto, sustentável.
Oskar Austegard
5
Dificilmente. Com a versão do LINQ, sei que o que estou fazendo é imutável e funciona em todos os elementos.
Tordek
6

Uma declaração não faz com que nenhum código seja executado, portanto, não é um problema de desempenho.

O segundo é o que você quer dizer, e é menos provável que você cometa um erro estúpido se fizer da segunda maneira, então use-o. Sempre tente declarar variáveis ​​no menor escopo necessário.

Além disso, a melhor maneira é usar o Linq:

List<User> users = l.Select(name => new User{ Name = name }).ToList();
Mark Byers
fonte
2
Eu adoro a frase "Sempre tente declarar variáveis ​​no menor escopo necessário." Acho que uma única linha pode responder muito bem à pergunta.
Manjoor 02 de
5

Sempre que você tiver uma dúvida sobre o desempenho, a única coisa a fazer é medir - executar um loop em torno do seu teste e cronometrá-lo.

Para responder à sua pergunta - sem medir :-) ou olhando para o ilasm gerado - qualquer diferença não seria perceptível em um número significativo de iterações e a operação mais cara em seu código provavelmente será a alocação do usuário por alguns pedidos de magnitude, então concentre-se na clareza do código (como você deve fazer em geral) e vá com 2.

Oh, é tarde e acho que só estou tentando dizer não se preocupe com esse tipo de coisa ou se envolva em detalhes como esse.

K

Kevin shea
fonte
obrigado pela dica, acho que vou cronometrar algumas outras coisas que venho pensando também hehe: D
Marcus
Se você quiser ir mais longe na análise do que afeta o desempenho, use um criador de perfil de código. No mínimo, ele começará a dar uma ideia de que tipo de código e operações demoram mais. ProfileSharp e EqatecProfilers são gratuitos e suficientes para você começar.
Kevin Shea
1

O segundo é melhor. Você pretende ter um novo usuário em cada iteração.

Jarrett Widman
fonte
1

Tecnicamente, o primeiro exemplo economizará alguns nanossegundos porque o frame da pilha não terá que ser movido para alocar uma nova variável, mas esta é uma quantidade tão pequena de tempo de CPU que você não notará, isto é, se o compilador não otimize qualquer diferença de qualquer maneira.

Erik Funkenbusch
fonte
Tenho certeza que o CLR não aloca "uma nova variável" em cada iteração de um loop.
dtb
Bem, o compilador pode muito bem otimizar isso, mas o espaço da pilha deve ser alocado para quaisquer variáveis ​​em um loop. Isso seria dependente da implementação e uma implementação pode simplesmente manter o quadro de pilha igual, enquanto outra (digamos Mono) poderia liberar a pilha e recriá-la em cada loop.
Erik Funkenbusch
16
Todas as variáveis ​​locais em um método (nível superior ou aninhadas em um loop) são compiladas para variáveis ​​de nível de método em IL. O espaço para as variáveis ​​é alocado antes de o método ser executado, não quando um branch com a declaração em C # é alcançado.
dtb
1
@dtb Você tem uma fonte para esta reclamação?
styfle
1

Nesse cenário, a segunda versão é melhor.

Em geral, se você só precisa acessar o valor dentro do corpo da iteração, escolha a segunda versão. Por outro lado, se houver algum estado final, a variável manterá além do corpo do loop, então declare e use a primeira versão.

csj
fonte
0

Não deve haver nenhuma diferença perceptível no desempenho.

Jacob Adams
fonte
0

Fui verificar esse problema. Surpreendentemente, descobri em meus testes sujos que a segunda opção é um pouco mais rápida o tempo todo.

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    void Method1()
    {
      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }
    }

    void Method2()
    {

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }
    }
  }

  public class User { public string Name; }
}

Verifiquei o CIL, mas não é idêntico.

insira a descrição da imagem aqui

Então preparei algo que queria ser um teste muito melhor.

namespace Test
{
  class Loop
  { 

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    public void Method1()
    {
      sw.Restart();

      C c;
      C c1;
      C c2;
      C c3;
      C c4;

      int i = 1000;
      while (i-- > 0)
      {
        c = new C();
        c1 = new C();
        c2 = new C();
        c3 = new C();
        c4 = new C();        
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    public void Method2()
    {
      sw.Restart();

      int i = 1000;
      while (i-- > 0)
      {
        var c = new C();
        var c1 = new C();
        var c2 = new C();
        var c3 = new C();
        var c4 = new C();
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  class C { }
}

Também neste caso o 2º método sempre estava ganhando, mas depois verifiquei o CIL não encontrando diferença.

insira a descrição da imagem aqui

Não sou um guru da leitura do CIL, mas não vejo nenhum problema de decleração. Como já foi apontado a declaração não é alocação, portanto não há penalidade de desempenho sobre ela.

Teste

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    void Method1()
    {
      sw.Restart();

      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    void Method2()
    {
      sw.Restart();

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  public class User { public string Name; }
Ucho
fonte