不执行 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));
}
不应再发生额外分配。
在使用 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));
}
不应再发生额外分配。