Database
 sql >> база данни >  >> RDS >> Database

Foreach или For – това е въпросът

Дискусията за разликата в предпочитанията между FOREACH и FOR не е нова. Всички знаем, че FOREACH е по-бавен, но не всички знаят защо.

Когато започнах да уча .NET, един човек ми каза, че FOREACH е два пъти по-бавен от FOR. Той каза това без никакви основания. Приех го за даденост.

В крайна сметка реших да проуча разликата в производителността на цикъла FOREACH и FOR и да напиша тази статия, за да обсъдя нюансите.
Нека да разгледаме следния код:

foreach (var item in Enumerable.Range(0, 128))
{
  Console.WriteLine(item);
}

FOREACH е синтактична захар. В този конкретен случай компилаторът го трансформира в следния код:

IEnumerator<int> enumerator = Enumerable.Range(0, 128).GetEnumerator();
try
 {
   while (enumerator.MoveNext())
   {
     int item = enumerator.Current;
     Console.WriteLine(item);
   }
 }
finally
 {
  if (enumerator != null)
  {
   enumerator.Dispose();
  }
}

Знаейки това, можем да предположим причината, поради която FOREACH е по-бавен от FOR:

  • Създава се нов обект. Нарича се Създател.
  • Методът MoveNext се извиква при всяка итерация.
  • Всяка итерация има достъп до свойството Current.

Това е! Не всичко обаче е толкова лесно, колкото звучи.

За щастие (или за съжаление), C#/CLR може да извършва оптимизации по време на изпълнение. Плюсът е, че кодът работи по-бързо. Лошите – разработчиците трябва да са наясно с тези оптимизации.

Масивът е тип, дълбоко интегриран в CLR и CLR предоставя редица оптимизации за този тип. Цикълът FOREACH е итерируем обект, който е ключов аспект на производителността. По-нататък в статията ще обсъдим как да преглеждате масиви и списъци с помощта на статичния метод Array.ForEach и метода List.ForEach.

Методи за изпитване

static double ArrayForWithoutOptimization(int[] array)
{
   int sum = 0;
   var watch = Stopwatch.StartNew();
   for (int i = 0; i < array.Length; i++)
     sum += array[i];
    watch.Stop();
    return watch.Elapsed.TotalMilliseconds;
}

static double ArrayForWithOptimization(int[] array)
{
   int length = array.Length;
   int sum = 0;
   var watch = Stopwatch.StartNew();
    for (int i = 0; i < length; i++)
      sum += array[i];
    watch.Stop();
     return watch.Elapsed.TotalMilliseconds;
}

static double ArrayForeach(int[] array)
{
  int sum = 0;
  var watch = Stopwatch.StartNew();
   foreach (var item in array)
    sum += item;
  watch.Stop();
  return watch.Elapsed.TotalMilliseconds;
}

static double ArrayForEach(int[] array)
{
  int sum = 0;
  var watch = Stopwatch.StartNew();
  Array.ForEach(array, i => { sum += i; });
  watch.Stop();
  return watch.Elapsed.TotalMilliseconds;
}

Условия за тестване:

  • Опцията „Оптимизиране на кода“ е включена.
  • Броят на елементите е равен на 100 000 000 (както в масива, така и в списъка).
  • Спецификация на компютъра:Intel Core i-5 и 8 GB RAM.

Масиви

Диаграмата показва, че FOR и FOREACH прекарват еднакво време, докато итерират през масиви. И това е така, защото CLR оптимизацията преобразува FOREACH в FOR и използва дължината на масива като максимална граница на итерацията. Няма значение дали дължината на масива е кеширана или не (при използване на FOR), резултатът е почти същият.

Може да звучи странно, но кеширането на дължината на масива може да повлияе на производителността. Докато използвате масив .Дължина като граница на итерацията, JIT тества индекса за попадане в дясната граница отвъд цикъла. Тази проверка се извършва само веднъж.
Много е лесно да се унищожи тази оптимизация. Случаят, когато променливата е кеширана, почти не се оптимизира.

Масив.foreach показа най-лошите резултати. Изпълнението му е доста просто:

public static void ForEach<T>(T[] array, Action<T> action)
 {
  for (int index = 0; index < array.Length; ++index)
    action(array[index]);
 }

Тогава защо работи толкова бавно? Използва FOR под капака. Е, причината е в извикването на делегата на ACTION. Всъщност при всяка итерация се извиква метод, което намалява производителността. Освен това делегатите не се извикват толкова бързо, колкото бихме искали.

Списъци

Резултатът е съвсем различен. Когато итерирате списъци, FOR и FOREACH показват различни резултати. Няма оптимизация. FOR (с кеширане на дължината на списъка) показва най-добрия резултат, докато FOREACH е повече от 2 пъти по-бавен. Това е така, защото се занимава с MoveNext и Current под капака. List.ForEach, както и Array.ForEach показва най-лошия резултат. Делегатите винаги се извикват виртуално. Реализацията на този метод изглежда така:

public void ForEach(Action<T> action)
{
  int num = this._version;
   for (int index = 0; index < this._size && num == this._version; ++index)
     action(this._items[index]);
   if (num == this._version)
     return;
   ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}

Всяка итерация извиква делегат на действие. Той също така проверява дали списъкът е променен и ако е така, се генерира изключение.

List вътрешно използва модел, базиран на масив, а методът ForEach използва индекса на масива, за да премине през него, което е значително по-бързо от използването на индексатора.

Конкретни числа

  1. Цикълът FOR без кеширане на дължината и FOREACH работят малко по-бързо върху масиви, отколкото FOR с кеширане на дължина.
  2. Масив.Foreach производителност е приблизително 6 пъти по-бавна от производителността на FOR / FOREACH.
  3. Цикълът FOR без кеширане на дължината работи 3 пъти по-бавно при списъци в сравнение с масивите.
  4. Цикълът FOR с кеширане на дължина работи 2 пъти по-бавно при списъци в сравнение с масивите.
  5. Цикълът FOREACH работи 6 пъти по-бавно при списъци в сравнение с масивите.

Ето табло с лидери за списъци:

И за масиви:

Заключение

Наистина ми хареса това разследване, особено процеса на писане, и се надявам, че и вие сте го харесали. Както се оказа, FOREACH е по-бърз за масиви, отколкото FOR с преследване на дължина. В списъчните структури FOREACH е по-бавен от FOR.

Кодът изглежда по-добре, когато използвате FOREACH, а съвременните процесори позволяват използването му. Въпреки това, ако трябва да оптимизирате вашата кодова база силно, по-добре е да използвате FOR.

Какво мислите, кой цикъл работи по-бързо, FOR или FOREACH?


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Релационни срещу нерелационни бази данни – част 2

  2. Оценка на кардиналност за множество предикати

  3. Разширен SQL:Вариации и различни случаи на употреба на T-SQL Insert Statement

  4. Съвпадение на предлагането с търсенето

  5. Как да получите записи от последните 30 дни