Оголошення змінної всередині або поза циклом foreach: що швидше / краще?


92

Який із них швидший / кращий?

Ось цей:

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

Мої навички розвитку новачка говорять мені, що перший краще, але мій друг каже мені неправильно, але не міг повідомити вагому причину, чому другий кращий.

Чи є якась різниця у продуктивності?

Відповіді:


112

З точки зору продуктивності обидва приклади компілюються в одному і тому ж ІЛ, тому різниці немає.

Другий - краще, оскільки він чіткіше виражає ваш намір, якщо uвін використовується лише всередині циклу.


10
Зверніть увагу , що є різниця , якщо змінна захоплюється лямбда - вираження або анонімного делегата; див. Зовнішня змінна пастка .
dtb

Чи можете ви пояснити, чому обидва вони компілюються в одному ІЛ? Я майже впевнений, що C # не піднімає оголошення змінних до верху функції, як це робить javascript.
styfle

4
@styfle ось відповідь на ваше запитання.
Девід Шеррет,

Після Stack Overflow посилання дають більш докладні відповіді: 1) Джон Ханна і 2) StriplingWarrior
user3613932

14

У будь-якому випадку, найкращим способом буде використання конструктора, який приймає Name ..., або, інакше, використовувати нотацію фігурних дужок:

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 я знаю, що те, що я роблю, є незмінним і працює над кожним елементом.
Тордек

6

Декларація не призводить до виконання будь-якого коду, тому це не проблема продуктивності.

Другий - це те, що ви маєте на увазі, і ви з меншою ймовірністю зробите дурну помилку, якщо зробите це другим способом, тому використовуйте це. Завжди намагайтеся оголошувати змінні в найменшому необхідному обсязі.

І крім того, кращий спосіб - використовувати Linq:

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

2
Мені подобається рядок "Завжди намагайтеся оголошувати змінні в найменшій необхідній області". Я думаю, що один рядок може дуже добре відповісти на питання.
Манджур

5

Кожного разу, коли у вас виникає запитання щодо продуктивності, єдине, що потрібно зробити, це виміряти - запустити цикл навколо тесту та визначити час.

Щоб відповісти на ваше запитання - не вимірюючи :-) або не дивлячись на сформований ілазм - будь-яка різниця не була б помітною у значній кількості ітерацій, і найдорожчою операцією у вашому коді, ймовірно, буде розподіл користувачів кількома замовленнями величини, тому зосередьтеся на чіткості коду (як це взагалі слід) і йдіть з 2.

О, пізно, і я здогадуюсь, я просто намагаюся сказати, не хвилюйтеся про подібні речі чи не потрапляйте в такі деталі.

К


THX за підказку, я думаю, що я буду час деякі інші речі я роздумував про а також хе-хе: 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.