Объявление переменной внутри или вне цикла foreach: что быстрее / лучше?


93

Какой из них быстрее / лучше?

Вот этот:

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

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

Или вот этот:

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

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

Мои навыки развития новичков говорят мне, что первый лучше, но мой друг сказал мне, что я ошибаюсь, но не смог мне объяснить, почему второй лучше.

Есть ли вообще разница в производительности?

Ответы:


114

С точки зрения производительности оба примера скомпилированы в один и тот же IL, поэтому разницы нет.

Второй вариант лучше, потому что он более четко выражает ваше намерение, если uиспользуется только внутри цикла.


10
Обратите внимание , что есть разница , если переменная захватывается лямбда - выражения или анонимного делегата; см. Ловушка внешней переменной .
dtb

Вы можете объяснить, почему оба скомпилированы в один и тот же IL? Я почти уверен, что C # не поднимает объявления переменных до вершины функции, как это делает javascript.
style

4
@styfle вот ответ на ваш вопрос.
Дэвид Шеррет

Следующие ссылки Stack Overflow предоставляют более подробные ответы: 1) Джон Ханна и 2) StriplingWarrior
user3613932

14

В любом случае, лучшим способом было бы использовать конструктор, который принимает имя ... или, иначе, использовать обозначение фигурных скобок:

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

или

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

или даже лучше, LINQ:

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

Теперь, хотя ваш первый пример в некоторых случаях может быть незаметно быстрее, второй лучше, потому что он более читабелен, и компилятор может отбросить переменную (и опустить ее вообще), поскольку она не используется за пределами области foreachдействия.


6
Некрофильный комментарий дня: «а еще лучше - LINQ». Конечно, это одна строчка кода, и это заставляет нас, разработчиков, чувствовать себя хорошо. Но четырехстрочная версия намного более понятна и, следовательно, удобна в обслуживании.
Оскар Остегард

5
Едва. В версии LINQ я знаю, что то, что я делаю, неизменяемо и работает над каждым элементом.
Tordek

6

Объявление не вызывает выполнения какого-либо кода, поэтому это не проблема производительности.

Второе - это то, что вы имеете в виду, и у вас меньше шансов сделать глупую ошибку, если вы сделаете это вторым способом, поэтому используйте его. Всегда старайтесь объявлять переменные в минимально необходимом объеме.

Кроме того, лучше использовать Linq:

List<User> users = l.Select(name => new User{ Name = name }).ToList();

2
Мне нравится строчка «Всегда старайтесь объявлять переменные в минимально необходимом масштабе». Я думаю, что одна строчка может очень хорошо ответить на вопрос.
Manjoor 02

5

Всякий раз, когда у вас возникает вопрос о производительности, единственное, что нужно сделать, - это измерить - запустить цикл вокруг вашего теста и установить время.

Чтобы ответить на ваш вопрос - без измерения :-) или просмотра сгенерированного ilasm - никакой разницы не будет заметно при значительном количестве итераций и самой дорогой операции в вашем коде, вероятно, будет распределение пользователей на несколько порядков значимости, поэтому сконцентрируйтесь на ясности кода (как и должно быть в целом) и используйте 2.

О, уже поздно, и я думаю, я просто пытаюсь сказать, не беспокойтесь о таких вещах и не вдавайтесь в подробности, подобные этим.

K


спасибо за подсказку, думаю, я найду время для некоторых других вещей, о которых я тоже волновался, хе-хе: D
Маркус

Если вы хотите пойти дальше и выяснить, что влияет на производительность, изучите возможность использования профилировщика кода. По крайней мере, это даст вам представление о том, какой тип кода и операции занимают больше всего времени. ProfileSharp и EqatecProfilers бесплатны и достаточны для начала работы.
Кевин Ши,


1

Технически, первый пример сэкономит несколько наносекунд, потому что кадр стека не нужно будет перемещать для выделения новой переменной, но это настолько крошечное количество процессорного времени, что вы этого не заметите, если компилятор этого не сделает. оптимизируйте любую разницу в любом случае.


Я почти уверен, что CLR не выделяет «новую переменную» на каждой итерации цикла.
dtb

Что ж, компилятор вполне может оптимизировать это, но пространство стека должно быть выделено для любых переменных в цикле. Это будет зависеть от реализации, и одна реализация может просто сохранить фрейм стека одинаковым, в то время как другая (например, Mono) может освободить стек, а затем воссоздать его в каждом цикле.
Эрик Функенбуш

16
Все локальные переменные в методе (верхнего уровня или вложенные в цикл) компилируются в переменные уровня метода в IL. Пространство для переменных выделяется перед выполнением метода, а не при достижении ветви с объявлением в C #.
dtb

1
@dtb У вас есть источник этого утверждения?
Styfle

1

В этом случае вторая версия лучше.

В общем, если вам нужно получить доступ только к значению в теле итерации, выберите вторую версию. С другой стороны, если есть какое-то конечное состояние, переменная будет удерживаться за пределами тела цикла, затем объявить, а затем использовать первую версию.




0

Я пошел проверить эту проблему. Удивительно, когда я обнаружил в своих грязных тестах, что второй вариант все время даже немного быстрее.

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

Я проверил CIL, но он не идентичен.

введите описание изображения здесь

Итак, я подготовил кое-что, что я хотел сделать гораздо лучше.

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

Также в этом случае всегда был выигрыш 2-й метод, но затем я проверил CIL, не обнаружив разницы.

введите описание изображения здесь

Я не гуру по чтению CIL, но не вижу проблем с отклонениями. Как уже отмечалось, объявление не является распределением, поэтому оно не снижает производительности.

Контрольная работа

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; }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.