委托实例分配与方法组相比 [英] Delegate instance allocation with method group compared to

查看:60
本文介绍了委托实例分配与方法组相比的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

几年前,我根据ReSharper的一些建议开始使用方法组语法,最近我尝试尝试

I started to use the method group syntax a couple of years ago based on some suggestion from ReSharper and recently I gave a try to ClrHeapAllocationAnalyzer and it flagged every location where I was using a method group in a lambda with the issue HAA0603 - This will allocate a delegate instance.

由于我很好奇这个建议是否真的有用,所以我为这两种情况编写了一个简单的控制台应用程序.

As I was curious to see if this suggestion was actually useful, I wrote a simple console app for the 2 cases.

代码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 Code2 的使用量为〜800MB .即使我们可以争论这个测试用例是否足以说明问题,它实际上也显示出差异.

Putting a break point on the Console.ReadKey(); of Code1 shows a memory consumption of ~500MB and on Code2 a consumption of ~800MB. Even if we can argue on whether this test case is good enough to explain something it actually shows a difference.

因此,我决定看一下生成的IL代码,以试图了解这两个代码之间的区别.

So I decided to have a look at the IL code produced to try to understand the difference between the 2 code.

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 Code2:

.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代码方面还不是足够的专家,无法真正完全理解它们之间的区别,所以这就是我提出此线程的原因.

I have to admit I am not enough expert in IL code to actually fully understand the difference and that's why I am raising this thread.

据我所知,实际的 Select 似乎在不通过方法组(Code1)完成时会生成更多指令,但BUT使用的是指向本机函数的指针.与总是生成新委托的其他情况相比,它是否通过指针重用了该方法?

As far as I have understood, the actual Select seems to generate more instructions when not done through a method group (Code1) BUT is using some pointer to native functions. Is it reusing the method through the pointer compared to the other case which is always generating a new delegate?

我还注意到,与Code1的IL代码相比,方法组IL(Code2)正在生成3个链接到 for 循环的注释.

Also I have noticed that the method group IL (Code2) is generating 3 comments linked to the for loop compared to the IL code of Code1.

在理解分配差异方面的任何帮助将不胜感激.

Any help in understanding the difference of allocation would be appreciated.

推荐答案

花一些时间了解为什么ReSharper建议使用方法组而不是lambda,并阅读

Spending some more time understanding why ReSharper is recommending to use method group instead of lambdas and reading the articles quoted in the rule page description, I am now able to answer my own question.

对于迭代次数足够少的情况(使用我提供的代码段,大约为1M)(大概是大多数情况),内存分配的差异足够小,因此这两种实现是等效的.此外,正如我们在生成的2个IL代码中所看到的那样,由于生成的指令较少,因此编译速度更快.请注意,这是ReSharper明确指出的:

For cases where the number of iterations is small enough, around 1M with the code snippet I provided (so probably the majority of cases), the difference in memory allocation is small enough so that the 2 implementations are equivalent. Besides, and as we can see in the 2 generated IL Codes the compilation is faster as there is less instructions to generate. Note this was clearly stated by ReSharper :

实现更紧凑的语法并防止使用lambda引起的编译时开销.

to achieve more compact syntax and prevent compile-time overhead caused by using lambdas.

其中解释了ReSharper的建议.

Which explain ReSharper recommendation.

但是,如果您知道委托将被大量使用,那么lambda是一个更好的选择.

But if you know that the delegate is going to be heavily used then the lambda is a better choice.

这篇关于委托实例分配与方法组相比的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆