Не выполняющий linq, вызывающий выделение памяти С#
При анализе распределения памяти .NET моего кода с мастером производительности Visual Studio 2013 я заметил определенную функцию, выделяющую много байтов (поскольку она вызывается в большом цикле). Но, глядя на функцию, выделенную в отчете профилирования, я не понимал, почему она вообще выделяет любую память.
Чтобы лучше понять, что произошло, я изолировал код, вызывающий выделение. Это было похоже на класс LinqAllocationTester ниже.
Как только я прокомментировал код LINQ в этой функции, который никогда не выполнялся в проверенном кодовом пути, больше памяти не было выделено.
Класс NonLinqAllocationTester имитирует это поведение. Замена кода LINQ на обычный цикл также не позволяет выделять память.
Если я запустил тест распределения памяти .NET на тестовом коде ниже, он показывает, что LinqAlocationTester вызывает 100 000 распределений (1 на вызов), а у NonLinqAllocationTester нет. Обратите внимание, что useLinq всегда false, поэтому сам код LINQ никогда не выполняется.
Function Name | Inclusive | Exclusive | Inclusive | Exclusive
| Allocations | Allocations | Bytes | Bytes
-------------------------------------------------------------------------------------
LinqAllocationTester.Test(int32) | 100.000 | 100.000 | 1.200.000 | 1.200.000
Program.Main(string[]) | 100.000 | 0 | 1.200.000 | 0
Итак, почему не выполняющий LINQ-код вызывает выделение памяти? И есть ли способ предотвратить это, кроме того, чтобы избежать функций LINQ?
class Program {
static void Main(string[] args) {
List<int> values = new List<int>() { 1, 2, 3, 4 };
LinqAllocationTester linqTester = new LinqAllocationTester(false, values);
NonLinqAllocationTester nonLinqTester = new NonLinqAllocationTester(false, values);
for (int i = 0; i < 100000; i++) {
linqTester.MaxDifference(i);
}
for (int i = 0; i < 100000; i++) {
nonLinqTester.MaxDifference(i);
}
}
}
internal class LinqAllocationTester {
private bool useLinq;
private List<int> values;
public LinqAllocationTester(bool useLinq, List<int> values) {
this.useLinq = useLinq;
this.values = values;
}
public int MaxDifference(int value) {
if (useLinq) {
return values.Max(x => Math.Abs(value - x));
} else {
int maxDifference = int.MinValue;
foreach (int value2 in values) {
maxDifference = Math.Max(maxDifference, Math.Abs(value - value2));
}
return maxDifference;
}
}
}
internal class NonLinqAllocationTester {
private bool useLinq;
private List<int> values;
public NonLinqAllocationTester(bool useLinq, List<int> values) {
this.useLinq = useLinq;
this.values = values;
}
public int MaxDifference(int value) {
if (useLinq) {
return 0;
} else {
int maxDifference = int.MinValue;
foreach (int value2 in values) {
maxDifference = Math.Max(maxDifference, Math.Abs(value - value2));
}
return maxDifference;
}
}
}
Ответы
Ответ 1
Вы можете заглянуть в сгенерированный IL, чтобы увидеть, что DisplayClass для выражения LINQ будет инициализирован в начале метода вне первой ветки if. Это потому, что он генерирует замыкание для лямбда-выражения в начале метода (где сначала появляется значение).
IL:
IL_0000: ldnull
IL_0001: stloc.2
IL_0002: newobj instance void ConsoleApplication2.LinqAllocationTester/'<>c__DisplayClass2'::.ctor()
IL_0007: stloc.3
IL_0008: ldloc.3
IL_0009: ldarg.1
IL_000a: stfld int32 ConsoleApplication2.LinqAllocationTester/'<>c__DisplayClass2'::'value'
IL_000f: nop
IL_0010: ldarg.0
IL_0011: ldfld bool ConsoleApplication2.LinqAllocationTester::useLinq
IL_0016: ldc.i4.0
IL_0017: ceq
IL_0019: stloc.s CS$4$0001
IL_001b: ldloc.s CS$4$0001
IL_001d: brtrue.s IL_0042
Если вы скопируете свое значение в более узкую область видимости:
if (useLinq)
{
int value2 = value;
return values.Max(x => Math.Abs(value2 - x));
}
дополнительные распределения больше не должны выполняться.