【发布时间】:2010-11-04 06:30:55
【问题描述】:
所以我在玩 ILDASM 时发现了一个奇怪的现象,我在 Google 上找不到很好的解释。
似乎在 VB.NET 中使用 With 块时,生成的 MSIL 大于 w/o。所以这让我问,With Blocks 真的更高效吗? MSIL 是 JIT 到本机机器代码的原因,因此更小的代码大小应该意味着更高效的代码,对吧?
这是两个类(Class2 和 Class3)的示例,它们为 Class1 的实例设置了相同的值。 Class2 没有 With 块,而 Class3 使用 With。 Class1 有六个属性,涉及 6 个私有成员。每个成员都有一个特定的数据类型,都是这个测试用例的一部分。
Friend Class Class2
Friend Sub New()
Dim c1 As New Class1
c1.One = "foobar"
c1.Two = 23009
c1.Three = 3987231665
c1.Four = 2874090071765301873
c1.Five = 3.1415973801462975
c1.Six = "a"c
End Sub
End Class
Friend Class Class3
Friend Sub New()
Dim c1 As New Class1
With c1
.One = "foobar"
.Two = 23009
.Three = 3987231665
.Four = 2874090071765301873
.Five = 3.1415973801462975
.Six = "a"c
End With
End Sub
End Class
这是 Class2 的结果 MSIL:
.method assembly specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 84 (0x54)
.maxstack 2
.locals init ([0] class WindowsApplication1.Class1 c1)
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: newobj instance void WindowsApplication1.Class1::.ctor()
IL_000b: stloc.0
IL_000c: ldloc.0
IL_000d: ldstr "foobar"
IL_0012: callvirt instance void WindowsApplication1.Class1::set_One(string)
IL_0017: ldloc.0
IL_0018: ldc.i4 0x59e1
IL_001d: callvirt instance void WindowsApplication1.Class1::set_Two(int16)
IL_0022: ldloc.0
IL_0023: ldc.i4 0xeda853b1
IL_0028: callvirt instance void WindowsApplication1.Class1::set_Three(uint32)
IL_002d: ldloc.0
IL_002e: ldc.i8 0x27e2d1b1540c3a71
IL_0037: callvirt instance void WindowsApplication1.Class1::set_Four(uint64)
IL_003c: ldloc.0
IL_003d: ldc.r8 3.1415973801462975
IL_0046: callvirt instance void WindowsApplication1.Class1::set_Five(float64)
IL_004b: ldloc.0
IL_004c: ldc.i4.s 97
IL_004e: callvirt instance void WindowsApplication1.Class1::set_Six(char)
IL_0053: ret
} // end of method Class2::.ctor
这是 Class3 的 MSIL:
.method assembly specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 88 (0x58)
.maxstack 2
.locals init ([0] class WindowsApplication1.Class1 c1,
[1] class WindowsApplication1.Class1 VB$t_ref$L0)
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: newobj instance void WindowsApplication1.Class1::.ctor()
IL_000b: stloc.0
IL_000c: ldloc.0
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: ldstr "foobar"
IL_0014: callvirt instance void WindowsApplication1.Class1::set_One(string)
IL_0019: ldloc.1
IL_001a: ldc.i4 0x59e1
IL_001f: callvirt instance void WindowsApplication1.Class1::set_Two(int16)
IL_0024: ldloc.1
IL_0025: ldc.i4 0xeda853b1
IL_002a: callvirt instance void WindowsApplication1.Class1::set_Three(uint32)
IL_002f: ldloc.1
IL_0030: ldc.i8 0x27e2d1b1540c3a71
IL_0039: callvirt instance void WindowsApplication1.Class1::set_Four(uint64)
IL_003e: ldloc.1
IL_003f: ldc.r8 3.1415973801462975
IL_0048: callvirt instance void WindowsApplication1.Class1::set_Five(float64)
IL_004d: ldloc.1
IL_004e: ldc.i4.s 97
IL_0050: callvirt instance void WindowsApplication1.Class1::set_Six(char)
IL_0055: ldnull
IL_0056: stloc.1
IL_0057: ret
} // end of method Class3::.ctor
我一眼就能看出的唯一主要区别是使用ldloc.1 操作码而不是ldloc.0。根据 MSDN,这两者之间的差异可以忽略不计,ldloc.0 是使用ldloc 访问索引 0 处的局部变量的有效方法,ldloc.1 相同,仅用于索引 1。
请注意,Class3 的代码大小是 88 对 84。这些来自发布/优化版本。内置于 VB Express 2010、.NET 4.0 Framework Client Profile。
想法?
编辑:
想为那些在这个线程上绊倒的人添加答案的一般要点,据我所知。
With ... End With的合理使用:
With ObjectA.Property1.SubProperty7.SubSubProperty4
.SubSubSubProperty1 = "Foo"
.SubSubSubProperty2 = "Bar"
.SubSubSubProperty3 = "Baz"
.SubSubSubProperty4 = "Qux"
End With
With ... End With 的不合理使用:
With ObjectB
.Property1 = "Foo"
.Property2 = "Bar"
.Property3 = "Baz"
.Property4 = "Qux"
End With
原因是因为在 ObjectA 的示例中,您将减少几个成员,并且该成员的每个解析都需要一些工作,因此只需解析一次引用并将最终引用粘贴到临时变量中(这就是全部With 确实如此),这加快了访问隐藏在该对象深处的属性/方法。
ObjectB 效率不高,因为您只深入了一层。每个分辨率都与访问由With 语句创建的临时引用大致相同,因此性能几乎没有提升。
【问题讨论】:
-
“所以更小的代码大小应该意味着更高效的代码,对吧?”哈哈……没有。
-
我认为 with/end with 只是为了方便!
-
这看起来不像是优化的构建。特别是
stloc.x/ldloc.x组合应该被优化掉。 (stloc.x弹出到x本地,ldloc.0将其加载回来) -
是的,它来自调试版本,但我正在测试项目中的 With blocks at work,并注意到即使使用发布版本,ILDASM 中给定函数/方法的代码大小也是仍然比没有更大,这(当时)让我质疑发生了什么。