不执行的 LINQ 引起了内存分配 C#

14

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

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

一旦我在该函数中注释掉 LINQ 代码(在测试代码路径中从未执行过),就不再分配内存。NonLinqAllocationTester 类模拟了这种行为。将 LINQ 代码替换为普通循环也不会导致内存分配。

如果我在下面的测试代码上运行 .NET 内存分配测试,它会显示 LinqAllocationTester 导致了 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个回答

7
你可以查看生成的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$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));
}

额外的分配不应再发生。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接