为什么当没有关闭时,编译器会为代理添加一个额外的参数? [英] Why the compiler adds an extra parameter for delegates when there is no closure?
问题描述
我正在玩代表
,并注意到当我创建一个 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屋!