Не выполняющий 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));
}

дополнительные распределения больше не должны выполняться.