Почему локальная ссылка var вызывает значительное снижение производительности?


рассмотрим следующую простую программу:

using System;
using System.Diagnostics;

class Program
{
   private static void Main(string[] args)
   {
      const int size = 10000000;
      var array = new string[size];

      var str = new string('a', 100);
      var sw = Stopwatch.StartNew();
      for (int i = 0; i < size; i++)
      {
         var str2 = new string('a', 100);
         //array[i] = str2; // This is slow
         array[i] = str; // This is fast
      }
      sw.Stop();
      Console.WriteLine("Took " + sw.ElapsedMilliseconds + "ms.");
   }
}

Если я запускаю это, это относительно быстро. Если я раскомментирую "медленную" линию и закомментирую "быструю" линию, это более чем в 5 раз медленнее. Обратите внимание, что в обеих ситуациях он инициализирует строку "str2" внутри цикла. Это не оптимизировано в любом случае (это можно проверить, посмотрев на IL или разборку).

код, казалось бы, делать тот же объем работы в любом случае. Для этого нужно выделите / инициализируйте строку, а затем назначьте ссылку на расположение массива. Единственное различие заключается в том, является ли эта ссылка локальным var "str" или "str2".

почему это делает такую большую разницу в производительности, назначая ссылку на " str "против " str2"?

Если мы посмотрим на разборки, есть разница:

(fast)
     var str2 = new string('a', 100);
0000008e  mov         r8d,64h 
00000094  mov         dx,61h 
00000098  xor         ecx,ecx 
0000009a  call        000000005E393928 
0000009f  mov         qword ptr [rsp+58h],rax 
000000a4  nop

(slow)
     var str2 = new string('a', 100);
00000085  mov         r8d,64h 
0000008b  mov         dx,61h 
0000008f  xor         ecx,ecx 
00000091  call        000000005E383838 
00000096  mov         qword ptr [rsp+58h],rax 
0000009b  mov         rax,qword ptr [rsp+58h] 
000000a0  mov         qword ptr [rsp+38h],rax

"медленная" версия имеет две дополнительные операции "mov", где" быстрая "версия просто имеет"nop".

может кто-нибудь объяснит, что здесь происходит? Трудно понять, как две дополнительные операции mov могут вызвать замедление >5x, тем более, что я ожидаю, что большая часть времени должна быть потрачена на инициализацию строки. Спасибо за любые идеи.

2 51

2 ответа:

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

но сборщик мусора в конечном итоге делает очень разные вещи в двух случаях.

на str версия, не более двух экземпляров строки живы в данный момент времени. Это означает (почти), что все новые объекты в поколении 0 умирают, ничего не нужно продвигать в поколение 1. Начиная с версии 1 не растет вообще, ГК не имеет никаких оснований, чтобы попытаться дорогие "полный коллекции."

на str2 версия, все новые экземпляры string живы. Объекты продвигаются к более высоким поколениям (что может включать их перемещение в памяти). Кроме того, поскольку более высокие поколения сейчас растут, GC иногда будет пытаться запустить полные коллекции.

обратите внимание, что .NET GC имеет тенденцию занимать время, линейное количеству живых объектов: живые объекты должны быть пройдены и перемещены с пути, в то время как мертвые объекты вообще ничего не стоят (они просто получить перезаписаны в следующий раз, когда память выделяется).

это значит str Это в лучшем случае для производительности сборщика мусора; в то время как str2 это наихудший вариант.

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

нет, локальная ссылка не медленная.

то, что медленно, создает тонны новых экземпляров строк, которые являются классами. В то время как быстрая версия использует тот же экземпляр. Это также может быть оптимизировано прочь, в то время как вызов конструктора не может.