Почему локальная ссылка 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 ответа:
вы правы, что код выполняет примерно одинаковый объем работы в любом случае.
но сборщик мусора в конечном итоге делает очень разные вещи в двух случаях.
на
str
версия, не более двух экземпляров строки живы в данный момент времени. Это означает (почти), что все новые объекты в поколении 0 умирают, ничего не нужно продвигать в поколение 1. Начиная с версии 1 не растет вообще, ГК не имеет никаких оснований, чтобы попытаться дорогие "полный коллекции."на
str2
версия, все новые экземпляры string живы. Объекты продвигаются к более высоким поколениям (что может включать их перемещение в памяти). Кроме того, поскольку более высокие поколения сейчас растут, GC иногда будет пытаться запустить полные коллекции.обратите внимание, что .NET GC имеет тенденцию занимать время, линейное количеству живых объектов: живые объекты должны быть пройдены и перемещены с пути, в то время как мертвые объекты вообще ничего не стоят (они просто получить перезаписаны в следующий раз, когда память выделяется).
это значит
str
Это в лучшем случае для производительности сборщика мусора; в то время какstr2
это наихудший вариант.посмотри счетчики производительности GC для вашей программы, я подозреваю, что вы увидите очень разные результаты между программами.