为什么当没有关闭时,编译器会为代理添加一个额外的参数? [英] Why the compiler adds an extra parameter for delegates when there is no closure?

查看:214
本文介绍了为什么当没有关闭时,编译器会为代理添加一个额外的参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在玩代表,并注意到当我创建一个 Func< int,int,int> 下面的例子:

  Func< int,int,int> func1 =(x,y)=> x * y; 

编译器生成的方法的签名不是我预期的:





你可以看到它需要一个对象,因为它是第一个参数。但是当有一个关闭时:

  int z = 10; 
Func< int,int,int> func1 =(x,y)=> x * y * z;

任务按预期工作:



< img src =https://i.stack.imgur.com/j9Za9.pngalt =enter image description here>



这是IL代码具有额外参数的方法:

  .method private hidebysig static int32'< Main> b__0'(object A_0,
int32 x,
int32 y)cil managed
{
.custom instance void [mscorlib] System.Runtime.CompilerServices.CompilerGeneratedAttribute ::。ctor()=(01 00 00 00)
//代码大小8(0x8)
.maxstack 2
.locals init([0] int32 V_0)
IL_0000:ldarg.1
IL_0001:ldarg.2
IL_0002:mul
IL_0003:stloc.0
IL_0004:br.s IL_0006
IL_0006:ldloc.0
IL_0007:ret
} // end的方法Program ::'< Main> b__0'

参数 A_0 甚至没有使用。所以在第一种情况下,对象参数的用途是什么?为什么在关闭时不添加?



注意:如果您有更好的主题,请随时编辑。



注2:我编译了 Debug 发布模式,没有区别。但是我在 Debug 模式中编译了第二个,以获得一个关闭行为,因为它优化了 Release 模式中的局部变量。 p>

注意3:我正在使用 Visual Studio 2014 CTP



编辑:这是第一种情况下 Main 的生成代码:

  .method private hidebysig static void Main(string [] args)cil managed 
{
.entrypoint
//代码大小30(0x1e)
.maxstack 2
.locals init([0] class [mscorlib] System.Func`3< int32,int32,int32> func1)
IL_0000:nop
IL_0001:ldsfld类[mscorlib] System.Func`3< int32,int32,int32> ConsoleApplication9.Program ::'CS $<> 9__CachedAnonymousMethodDelegate1'
IL_0006:dup
IL_0007:brtrue.s IL_001c
IL_0009:pop
IL_000a:ldnull
IL_000b :ldftn int32 ConsoleApplication9.Program ::'< Main> b__0'(object,
int32,
int32)
IL_0011:newobj instance void class [mscorlib] System.Func`3< int32 ,int32,int32> ::。ctor(object,
native int)
IL_0016:dup
IL_0017:stsfld class [mscorlib] System.Func`3< int32,int32,int32> ConsoleApplication9.Program ::'CS $<> 9 _CachedAnonymousMethodDelegate1'
IL_001c:stloc.0
IL_001d:ret
} //方法结束程序::主


解决方案

虽然这可能看起来非常令人惊讶,但快速搜索表明这是出于性能原因。



关于它的错误报告,请指出代表没有隐含的这个比具有一个隐含的这个的代理要慢得多,有一个隐含的这个需要做一些复杂的参数洗牌,每当调用委托时:



假设你调用 func1(1,2)。这看起来像(伪代码,而不是CIL)

  push func1 
push 1
push 2
调用Func< ,,> ::调用

当这个 func1 已知绑定到一个静态函数,取两个 int 值,然后需要执行相当于

  push arg.1 
push arg.2
调用方法

  arg.0 = arg.1 
arg.1 = arg.2
jmp方法

func1 已知绑定到一个静态函数,取$ null 和两个 int ,它只需要执行相当于

  arg.0 = null 
jmp方法

由于环境已经完全设置为输入引用类型的函数,而两个 int 值。



是的,这是一个微型优化,它的典型盟友没有关系,但每个人都受益匪浅,包括那些在事情上遇到困难的人。


I was playing with delegates and noticed that when I create a Func<int,int,int> like the example below:

Func<int, int, int> func1 = (x, y) => x * y;

The signature of the compiler generated method is not what I expected:

As you can see it takes an object for it's first parameter. But when there is a closure:

int z = 10;
Func<int, int, int> func1 = (x, y) => x * y * z;

Everthing works as expected:

This is the IL code for the method with extra parameter:

    .method private hidebysig static int32  '<Main>b__0'(object A_0,
                                                     int32 x,
                                                     int32 y) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       8 (0x8)
  .maxstack  2
  .locals init ([0] int32 V_0)
  IL_0000:  ldarg.1
  IL_0001:  ldarg.2
  IL_0002:  mul
  IL_0003:  stloc.0
  IL_0004:  br.s       IL_0006
  IL_0006:  ldloc.0
  IL_0007:  ret
} // end of method Program::'<Main>b__0'

It seems that the parameter A_0 is not even used.So, what is the purpose of the object parameter in the first case? Why it isn't added when there is a closure?

Note: If you have a better idea for title please feel free to edit.

Note 2: I compiled first code in both Debug and Release modes, there was no difference. But I compiled second in Debug mode to get a closure behaviour since it optimizes the local variable in Release mode.

Note 3: I'm using Visual Studio 2014 CTP.

Edit: This is the generated code for Main in the first case:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       30 (0x1e)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Func`3<int32,int32,int32> func1)
  IL_0000:  nop
  IL_0001:  ldsfld     class [mscorlib]System.Func`3<int32,int32,int32> ConsoleApplication9.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_0006:  dup
  IL_0007:  brtrue.s   IL_001c
  IL_0009:  pop
  IL_000a:  ldnull
  IL_000b:  ldftn      int32 ConsoleApplication9.Program::'<Main>b__0'(object,
                                                                       int32,
                                                                       int32)
  IL_0011:  newobj     instance void class [mscorlib]System.Func`3<int32,int32,int32>::.ctor(object,
                                                                                             native int)
  IL_0016:  dup
  IL_0017:  stsfld     class [mscorlib]System.Func`3<int32,int32,int32> ConsoleApplication9.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_001c:  stloc.0
  IL_001d:  ret
} // end of method Program::Main

解决方案

Although this may seem highly surprising, a quick search shows that it's for performance reasons.

On a bug report about it, it's pointed out that delegates to that have no implicit this are measurably slower than delegates that do have an implicit this, because delegates that don't have an implicit this need to do a bit of complicated argument shuffling whenever the delegate is invoked:

Suppose you call func1(1, 2). This looks like (pseudo-code, not CIL)

push func1
push 1
push 2
call Func<,,>::Invoke

When this func1 is known to be bound to a static function taking two int values, it then needs to perform the equivalent of either

push arg.1
push arg.2
call method

or

arg.0 = arg.1
arg.1 = arg.2
jmp method

Whereas when func1 is known to be bound to a static function taking null and two int values, it only needs to perform the equivalent of

arg.0 = null
jmp method

since the environment is already set up perfectly for entering a function taking a reference type and two int values.

Yes, it's a micro-optimisation that typically won't matter, but it's one that everyone benefits from, including those in situations where it does matter.

这篇关于为什么当没有关闭时,编译器会为代理添加一个额外的参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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