委托实例分配与方法组相比
Delegate instance allocation with method group compared to
几年前,我根据 ReSharper 的一些建议开始使用方法组语法,最近我尝试了 ClrHeapAllocationAnalyzer,它标记了我使用方法组的每个位置问题 HAA0603 - This will allocate a delegate instance
.
的 lambda
因为我很好奇这个建议是否真的有用,所以我为这 2 个案例编写了一个简单的控制台应用程序。
代码 1:
class Program
{
static void Main(string[] args)
{
var temp = args.AsEnumerable();
for (int i = 0; i < 10_000_000; i++)
{
temp = temp.Select(x => Foo(x));
}
Console.ReadKey();
}
private static string Foo(string x)
{
return x;
}
}
代码2:
class Program
{
static void Main(string[] args)
{
var temp = args.AsEnumerable();
for (int i = 0; i < 10_000_000; i++)
{
temp = temp.Select(Foo);
}
Console.ReadKey();
}
private static string Foo(string x)
{
return x;
}
}
在 Code1 的 Console.ReadKey();
处打断点显示 ~500MB 和 [=43] 的内存消耗=]Code2一次消耗~800MB。即使我们可以争论这个测试用例是否足以解释某些事情,它实际上也显示出差异。
所以我决定看一下生成的 IL 代码,以尝试理解这两个代码之间的区别。
IL 代码 1:
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 75 (0x4b)
.maxstack 3
.entrypoint
.locals init (
[0] class [mscorlib]System.Collections.Generic.IEnumerable`1<string>,
[1] int32,
[2] bool
)
// temp = from x in temp
// select Foo(x);
IL_0000: nop
// IEnumerable<string> temp = args.AsEnumerable();
IL_0001: ldarg.0
IL_0002: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::AsEnumerable<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_0007: stloc.0
// for (int i = 0; i < 10000000; i++)
IL_0008: ldc.i4.0
IL_0009: stloc.1
// (no C# code)
IL_000a: br.s IL_0038
// loop start (head: IL_0038)
IL_000c: nop
IL_000d: ldloc.0
IL_000e: ldsfld class [mscorlib]System.Func`2<string, string> ConsoleApp1.Program/'<>c'::'<>9__0_0'
IL_0013: dup
IL_0014: brtrue.s IL_002d
IL_0016: pop
IL_0017: ldsfld class ConsoleApp1.Program/'<>c' ConsoleApp1.Program/'<>c'::'<>9'
IL_001c: ldftn instance string ConsoleApp1.Program/'<>c'::'<Main>b__0_0'(string)
IL_0022: newobj instance void class [mscorlib]System.Func`2<string, string>::.ctor(object, native int)
IL_0027: dup
IL_0028: stsfld class [mscorlib]System.Func`2<string, string> ConsoleApp1.Program/'<>c'::'<>9__0_0'
IL_002d: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!1> [System.Core]System.Linq.Enumerable::Select<string, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, !!1>)
IL_0032: stloc.0
IL_0033: nop
// for (int i = 0; i < 10000000; i++)
IL_0034: ldloc.1
IL_0035: ldc.i4.1
IL_0036: add
IL_0037: stloc.1
// for (int i = 0; i < 10000000; i++)
IL_0038: ldloc.1
IL_0039: ldc.i4 10000000
IL_003e: clt
IL_0040: stloc.2
// (no C# code)
IL_0041: ldloc.2
IL_0042: brtrue.s IL_000c
// end loop
// Console.ReadKey();
IL_0044: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_0049: pop
// (no C# code)
IL_004a: ret
} // end of method Program::Main
IL 代码 2:
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 56 (0x38)
.maxstack 3
.entrypoint
.locals init (
[0] class [mscorlib]System.Collections.Generic.IEnumerable`1<string>,
[1] int32,
[2] bool
)
// (no C# code)
IL_0000: nop
// IEnumerable<string> temp = args.AsEnumerable();
IL_0001: ldarg.0
IL_0002: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::AsEnumerable<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_0007: stloc.0
// for (int i = 0; i < 10000000; i++)
IL_0008: ldc.i4.0
IL_0009: stloc.1
// (no C# code)
IL_000a: br.s IL_0025
// loop start (head: IL_0025)
IL_000c: nop
// temp = temp.Select(Foo);
IL_000d: ldloc.0
IL_000e: ldnull
IL_000f: ldftn string ConsoleApp1.Program::Foo(string)
IL_0015: newobj instance void class [mscorlib]System.Func`2<string, string>::.ctor(object, native int)
IL_001a: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!1> [System.Core]System.Linq.Enumerable::Select<string, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, !!1>)
IL_001f: stloc.0
// (no C# code)
IL_0020: nop
// for (int i = 0; i < 10000000; i++)
IL_0021: ldloc.1
IL_0022: ldc.i4.1
IL_0023: add
IL_0024: stloc.1
// for (int i = 0; i < 10000000; i++)
IL_0025: ldloc.1
IL_0026: ldc.i4 10000000
IL_002b: clt
IL_002d: stloc.2
// (no C# code)
IL_002e: ldloc.2
IL_002f: brtrue.s IL_000c
// end loop
// Console.ReadKey();
IL_0031: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_0036: pop
// (no C# code)
IL_0037: ret
} // end of method Program::Main
我不得不承认我在 IL 代码方面不够专业,无法真正完全理解其中的区别,这就是我提出这个话题的原因。
据我所知,实际的 Select
似乎在未通过方法组 (Code1) 完成时生成更多指令,而是使用一些指向本机函数的指针。与总是生成新委托的另一种情况相比,它是否通过指针重用了方法?
我还注意到,与 Code1 的 IL 代码相比,方法组 IL (Code2) 正在生成 3 个链接到 for
循环的注释。
如能帮助理解分配差异,我们将不胜感激。
花更多时间了解为什么 ReSharper 推荐使用方法组而不是 lambda 并阅读 rule page description 中引用的文章,我现在可以回答我自己的问题了。
对于迭代次数足够小的情况,我提供的代码片段约为 1M(因此可能是大多数情况),内存分配的差异足够小,因此 2 种实现是等效的。此外,正如我们在 2 个生成的 IL 代码中看到的那样,编译速度更快,因为要生成的指令更少。请注意,ReSharper 明确指出了这一点:
to achieve more compact syntax and prevent compile-time overhead caused by using lambdas.
其中解释了 ReSharper 推荐。
但如果您知道代理将被大量使用,那么 lambda 是更好的选择。
几年前,我根据 ReSharper 的一些建议开始使用方法组语法,最近我尝试了 ClrHeapAllocationAnalyzer,它标记了我使用方法组的每个位置问题 HAA0603 - This will allocate a delegate instance
.
因为我很好奇这个建议是否真的有用,所以我为这 2 个案例编写了一个简单的控制台应用程序。
代码 1:
class Program
{
static void Main(string[] args)
{
var temp = args.AsEnumerable();
for (int i = 0; i < 10_000_000; i++)
{
temp = temp.Select(x => Foo(x));
}
Console.ReadKey();
}
private static string Foo(string x)
{
return x;
}
}
代码2:
class Program
{
static void Main(string[] args)
{
var temp = args.AsEnumerable();
for (int i = 0; i < 10_000_000; i++)
{
temp = temp.Select(Foo);
}
Console.ReadKey();
}
private static string Foo(string x)
{
return x;
}
}
在 Code1 的 Console.ReadKey();
处打断点显示 ~500MB 和 [=43] 的内存消耗=]Code2一次消耗~800MB。即使我们可以争论这个测试用例是否足以解释某些事情,它实际上也显示出差异。
所以我决定看一下生成的 IL 代码,以尝试理解这两个代码之间的区别。
IL 代码 1:
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 75 (0x4b)
.maxstack 3
.entrypoint
.locals init (
[0] class [mscorlib]System.Collections.Generic.IEnumerable`1<string>,
[1] int32,
[2] bool
)
// temp = from x in temp
// select Foo(x);
IL_0000: nop
// IEnumerable<string> temp = args.AsEnumerable();
IL_0001: ldarg.0
IL_0002: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::AsEnumerable<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_0007: stloc.0
// for (int i = 0; i < 10000000; i++)
IL_0008: ldc.i4.0
IL_0009: stloc.1
// (no C# code)
IL_000a: br.s IL_0038
// loop start (head: IL_0038)
IL_000c: nop
IL_000d: ldloc.0
IL_000e: ldsfld class [mscorlib]System.Func`2<string, string> ConsoleApp1.Program/'<>c'::'<>9__0_0'
IL_0013: dup
IL_0014: brtrue.s IL_002d
IL_0016: pop
IL_0017: ldsfld class ConsoleApp1.Program/'<>c' ConsoleApp1.Program/'<>c'::'<>9'
IL_001c: ldftn instance string ConsoleApp1.Program/'<>c'::'<Main>b__0_0'(string)
IL_0022: newobj instance void class [mscorlib]System.Func`2<string, string>::.ctor(object, native int)
IL_0027: dup
IL_0028: stsfld class [mscorlib]System.Func`2<string, string> ConsoleApp1.Program/'<>c'::'<>9__0_0'
IL_002d: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!1> [System.Core]System.Linq.Enumerable::Select<string, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, !!1>)
IL_0032: stloc.0
IL_0033: nop
// for (int i = 0; i < 10000000; i++)
IL_0034: ldloc.1
IL_0035: ldc.i4.1
IL_0036: add
IL_0037: stloc.1
// for (int i = 0; i < 10000000; i++)
IL_0038: ldloc.1
IL_0039: ldc.i4 10000000
IL_003e: clt
IL_0040: stloc.2
// (no C# code)
IL_0041: ldloc.2
IL_0042: brtrue.s IL_000c
// end loop
// Console.ReadKey();
IL_0044: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_0049: pop
// (no C# code)
IL_004a: ret
} // end of method Program::Main
IL 代码 2:
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 56 (0x38)
.maxstack 3
.entrypoint
.locals init (
[0] class [mscorlib]System.Collections.Generic.IEnumerable`1<string>,
[1] int32,
[2] bool
)
// (no C# code)
IL_0000: nop
// IEnumerable<string> temp = args.AsEnumerable();
IL_0001: ldarg.0
IL_0002: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::AsEnumerable<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_0007: stloc.0
// for (int i = 0; i < 10000000; i++)
IL_0008: ldc.i4.0
IL_0009: stloc.1
// (no C# code)
IL_000a: br.s IL_0025
// loop start (head: IL_0025)
IL_000c: nop
// temp = temp.Select(Foo);
IL_000d: ldloc.0
IL_000e: ldnull
IL_000f: ldftn string ConsoleApp1.Program::Foo(string)
IL_0015: newobj instance void class [mscorlib]System.Func`2<string, string>::.ctor(object, native int)
IL_001a: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!1> [System.Core]System.Linq.Enumerable::Select<string, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, !!1>)
IL_001f: stloc.0
// (no C# code)
IL_0020: nop
// for (int i = 0; i < 10000000; i++)
IL_0021: ldloc.1
IL_0022: ldc.i4.1
IL_0023: add
IL_0024: stloc.1
// for (int i = 0; i < 10000000; i++)
IL_0025: ldloc.1
IL_0026: ldc.i4 10000000
IL_002b: clt
IL_002d: stloc.2
// (no C# code)
IL_002e: ldloc.2
IL_002f: brtrue.s IL_000c
// end loop
// Console.ReadKey();
IL_0031: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_0036: pop
// (no C# code)
IL_0037: ret
} // end of method Program::Main
我不得不承认我在 IL 代码方面不够专业,无法真正完全理解其中的区别,这就是我提出这个话题的原因。
据我所知,实际的 Select
似乎在未通过方法组 (Code1) 完成时生成更多指令,而是使用一些指向本机函数的指针。与总是生成新委托的另一种情况相比,它是否通过指针重用了方法?
我还注意到,与 Code1 的 IL 代码相比,方法组 IL (Code2) 正在生成 3 个链接到 for
循环的注释。
如能帮助理解分配差异,我们将不胜感激。
花更多时间了解为什么 ReSharper 推荐使用方法组而不是 lambda 并阅读 rule page description 中引用的文章,我现在可以回答我自己的问题了。
对于迭代次数足够小的情况,我提供的代码片段约为 1M(因此可能是大多数情况),内存分配的差异足够小,因此 2 种实现是等效的。此外,正如我们在 2 个生成的 IL 代码中看到的那样,编译速度更快,因为要生成的指令更少。请注意,ReSharper 明确指出了这一点:
to achieve more compact syntax and prevent compile-time overhead caused by using lambdas.
其中解释了 ReSharper 推荐。
但如果您知道代理将被大量使用,那么 lambda 是更好的选择。