考虑一个返回Structure的方法,如下所示:
Public Function DoWork() As MyStructure
Return New MyStructure(1.5, 1.7, 1.1, 55.9)
End Function
在这种情况下,.NET是否创建并初始化MyStructure
值一次,或两次?
编辑:我的预感是,对DoWork
的调用必须涉及.NET从一开始就推送堆栈上的返回值。否则,Return
如何将任何内容返回给调用代码?这就是我所说的第一次初始化。
第二次初始化将在Return
语句中,其中参数 1.5,1.7,1.1,55.9 初始化新的MyStructure
值。在Return
上,.NET将使用新的返回值覆盖Stack上的现有值。
问题是,我对.NET的工作方式知之甚少。我对Stacks如何工作的概念是基于在DOS下,在80年代早期尝试在Pascal中编码的模糊回忆。我不知道.NET / Windows如何做这些事情!
答案 0 :(得分:5)
只需查看生成的机器代码即可查看发生的情况。您首先需要更改选项以确保启用优化程序,工具>选项>调试>一般>解开"抑制JIT优化"复选框。切换到发布版本。在DoWork调用上设置断点,当它命中时使用Debug> Windows>反汇编以查看生成的机器代码。
关于你所看到的,我会面糊一点。你的直觉是正确的,只返回简单的标量值是有效的,它们适合CPU寄存器。不是这种结构,调用方法必须在其堆栈帧上保留空间,以便被调用者可以在那里存储结构。它传递指向该保留空间的指针。换句话说,如果您将此方法声明为Public Sub DoWork(ByRef retval As MyStructure)
,则没有真正的区别。
只有通过内联函数才能优化此代码的其他方式。 x86抖动不会这样做,它对返回结构的方法很挑剔。事实上,x64抖动实际上是内联方法,但随后它完成了一个绝对可怕的工作。它仍然保留返回值的空间,然后生成许多不必要的初始化和值移动代码。非常残忍。这个抖动是为VS2015(项目名称RyuJIT)重写的,我还没有安装它,看看它是否做得更好。
绘制的基本结论:此代码未经优化。传递ByVal时,结构运行良好。不要让这个抽筋你的风格,我们在这里说纳秒。
答案 1 :(得分:0)
看看在IL中为BCL类型的结构做了什么,它似乎只创建了一次 Size.Truncate的示例
public static Size Truncate(SizeF value)
{
return new Size((int)value.Width, (int)value.Height);
}
.method public hidebysig static
valuetype System.Drawing.Size Truncate (
valuetype System.Drawing.SizeF 'value'
) cil managed
{
// Method begins at RVA 0x169d6
// Code size 22 (0x16)
.maxstack 8
IL_0000: ldarga.s 'value'
IL_0002: call instance float32 System.Drawing.SizeF::get_Width()
IL_0007: conv.i4
IL_0008: ldarga.s 'value'
IL_000a: call instance float32 System.Drawing.SizeF::get_Height()
IL_000f: conv.i4
IL_0010: newobj instance void System.Drawing.Size::.ctor(int32, int32)
IL_0015: ret
} // end of method Size::Truncate
您可以在IL_0010
newobj
操作码和documentation州注明:
创建一个新对象或值类型的新实例,将对象引用(类型O)推送到评估堆栈。