不执行 linq 导致内存分配 C#

non executing linq causing memory allocation C#

在使用 Visual Studio 2013 性能向导分析我的代码的 .NET 内存分配时,我注意到某个函数分配了大量字节(因为它是在大循环中调用的)。但是查看分析报告中突出显示的功能,我根本不明白为什么它会分配任何内存。

为了更好地理解发生了什么,我隔离了导致分配的代码。这类似于下面的 LinqAllocationTester class。

一旦我注释掉该函数中的 LINQ 代码,该函数从未在测试代码路径中执行过,就不再分配内存。 NonLinqAllocationTester class 模仿这种行为。用普通循环替换 LINQ 代码也不会分配内存。

如果我 运行 在下面的测试代码上进行 .NET 内存分配测试,它表明 LinqAllocationTester 导致 100.000 次分配(每次调用 1 次),而 NonLinqAllocationTester 有 none。 请注意 useLinq 始终为假,因此 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;
        }
    }
}

您可以查看生成的 IL,发现 LINQ 表达式的 DisplayClass 将在第一个 if 分支之外的方法开头初始化。那是因为它在方法的开头(值第一次出现的地方)为 lambda 表达式生成闭包。

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[=10=]01
IL_001b: ldloc.s CS[=10=]01
IL_001d: brtrue.s IL_0042

如果您像这样将值复制到范围更窄的变量:

if (useLinq)
{
    int value2 = value;
    return values.Max(x => Math.Abs(value2 - x));
}

不应再发生额外分配。