为什么封闭类型更快?
我想了解更深层次的细节,为什么这是正确的。
为什么封闭类型更快?
我想了解更深层次的细节,为什么这是正确的。
在最低层次上,当你有密封类时,编译器可以进行微小的优化。
如果你在密封类上调用方法,并且在编译时声明的类型是该密封类,编译器可以使用call指令来实现方法调用(大多数情况下),而不是用callvirt指令。这是因为方法目标不能被覆盖。Call 消除了空检查,并且比callvirt执行更快的虚函数表查找,因为它不必检查虚函数表。
这可能只会略微提高性能。
话虽如此,在决定是否要将类标记为sealed时完全可以忽略此点。将类型标记为sealed应该是一个设计决策,而不是一个性能决策。你是否希望人们(包括你自己)可能从你的类中构建子类?如果是,请不要将其标记为sealed。如果不是,请标记为sealed。这确实应该是决定因素。
实际上,这与它们不需要担心虚函数表的扩展有关;封闭类型无法扩展,因此,运行时不需要关心它们可能是多态的方式。
我决定发布小的代码示例,以说明C#编译器何时发出“call”和“callvirt”指令。
因此,这里是我使用的所有类型的源代码:
public sealed class SealedClass
{
public void DoSmth()
{ }
}
public class ClassWithSealedMethod : ClassWithVirtualMethod
{
public sealed override void DoSmth()
{ }
}
public class ClassWithVirtualMethod
{
public virtual void DoSmth()
{ }
}
同时,我还有一种方法可以调用所有的“DoSmth()”方法:
public void Call()
{
SealedClass sc = new SealedClass();
sc.DoSmth();
ClassWithVirtualMethod cwcm = new ClassWithVirtualMethod();
cwcm.DoSmth();
ClassWithSealedMethod cwsm = new ClassWithSealedMethod();
cwsm.DoSmth();
}
看一下 "Call()" 方法,我们可以说(理论上)C# 编译器应该发出 2 个 "callvirt" 和 1 个 "call" 指令,对吗?不幸的是,现实有些不同 - 会有 3 个 "callvirt":
.method public hidebysig instance void Call() cil managed
{
.maxstack 1
.locals init (
[0] class TestApp.SealedClasses.SealedClass sc,
[1] class TestApp.SealedClasses.ClassWithVirtualMethod cwcm,
[2] class TestApp.SealedClasses.ClassWithSealedMethod cwsm)
L_0000: newobj instance void TestApp.SealedClasses.SealedClass::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: callvirt instance void TestApp.SealedClasses.SealedClass::DoSmth()
L_000c: newobj instance void TestApp.SealedClasses.ClassWithVirtualMethod::.ctor()
L_0011: stloc.1
L_0012: ldloc.1
L_0013: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth()
L_0018: newobj instance void TestApp.SealedClasses.ClassWithSealedMethod::.ctor()
L_001d: stloc.2
L_001e: ldloc.2
L_001f: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth()
L_0024: ret
}
原因很简单:运行时必须在调用“DoSmth()”方法之前检查类型实例是否不等于null。 但是我们仍然可以以这样的方式编写代码,使得C#编译器能够发出优化的IL代码:
public void Call()
{
new SealedClass().DoSmth();
new ClassWithVirtualMethod().DoSmth();
new ClassWithSealedMethod().DoSmth();
}
结果如下:
.method public hidebysig instance void Call() cil managed
{
.maxstack 8
L_0000: newobj instance void TestApp.SealedClasses.SealedClass::.ctor()
L_0005: call instance void TestApp.SealedClasses.SealedClass::DoSmth()
L_000a: newobj instance void TestApp.SealedClasses.ClassWithVirtualMethod::.ctor()
L_000f: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth()
L_0014: newobj instance void TestApp.SealedClasses.ClassWithSealedMethod::.ctor()
L_0019: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth()
L_001e: ret
}
Animal
,它有一个方法makeNoise()
,则编译器不一定知道任何Animal
实例是否覆盖该方法。因此,每次任何Animal
实例调用makeNoise()
时,都需要检查实例的类层次结构,以查看实例是否在扩展类中覆盖了此方法。AnimalFeeder
,它有一个方法feedAnimal()
,那么编译器确切地知道这个方法无法被覆盖。它可以编译分支到子例程或等效指令而不是使用虚拟分派表。sealed
来防止从该类继承,也可以在基类中声明为virtual
的方法上使用sealed
来防止进一步覆盖该方法。public sealed class Sealed
{
public string Message { get; set; }
public void DoStuff() { }
}
public class Derived : Base
{
public sealed override void DoStuff() { }
}
public class Base
{
public string Message { get; set; }
public virtual void DoStuff() { }
}
static void Main()
{
Sealed sealedClass = new Sealed();
sealedClass.DoStuff();
Derived derivedClass = new Derived();
derivedClass.DoStuff();
Base BaseClass = new Base();
BaseClass.DoStuff();
}
MIL 代码
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 41 (0x29)
.maxstack 8
IL_0000: newobj instance void ConsoleApp1.Program/Sealed::.ctor()
IL_0005: callvirt instance void ConsoleApp1.Program/Sealed::DoStuff()
IL_000a: newobj instance void ConsoleApp1.Program/Derived::.ctor()
IL_000f: callvirt instance void ConsoleApp1.Program/Base::DoStuff()
IL_0014: newobj instance void ConsoleApp1.Program/Base::.ctor()
IL_0019: callvirt instance void ConsoleApp1.Program/Base::DoStuff()
IL_0028: ret
} // end of method Program::Main
--- C:\Users\Ivan Porta\source\repos\ConsoleApp1\Program.cs --------------------
{
0066084A in al,dx
0066084B push edi
0066084C push esi
0066084D push ebx
0066084E sub esp,4Ch
00660851 lea edi,[ebp-58h]
00660854 mov ecx,13h
00660859 xor eax,eax
0066085B rep stos dword ptr es:[edi]
0066085D cmp dword ptr ds:[5842F0h],0
00660864 je 0066086B
00660866 call 744CFAD0
0066086B xor edx,edx
0066086D mov dword ptr [ebp-3Ch],edx
00660870 xor edx,edx
00660872 mov dword ptr [ebp-48h],edx
00660875 xor edx,edx
00660877 mov dword ptr [ebp-44h],edx
0066087A xor edx,edx
0066087C mov dword ptr [ebp-40h],edx
0066087F nop
Sealed sealedClass = new Sealed();
00660880 mov ecx,584E1Ch
00660885 call 005730F4
0066088A mov dword ptr [ebp-4Ch],eax
0066088D mov ecx,dword ptr [ebp-4Ch]
00660890 call 00660468
00660895 mov eax,dword ptr [ebp-4Ch]
00660898 mov dword ptr [ebp-3Ch],eax
sealedClass.DoStuff();
0066089B mov ecx,dword ptr [ebp-3Ch]
0066089E cmp dword ptr [ecx],ecx
006608A0 call 00660460
006608A5 nop
Derived derivedClass = new Derived();
006608A6 mov ecx,584F3Ch
006608AB call 005730F4
006608B0 mov dword ptr [ebp-50h],eax
006608B3 mov ecx,dword ptr [ebp-50h]
006608B6 call 006604A8
006608BB mov eax,dword ptr [ebp-50h]
006608BE mov dword ptr [ebp-40h],eax
derivedClass.DoStuff();
006608C1 mov ecx,dword ptr [ebp-40h]
006608C4 mov eax,dword ptr [ecx]
006608C6 mov eax,dword ptr [eax+28h]
006608C9 call dword ptr [eax+10h]
006608CC nop
Base BaseClass = new Base();
006608CD mov ecx,584EC0h
006608D2 call 005730F4
006608D7 mov dword ptr [ebp-54h],eax
006608DA mov ecx,dword ptr [ebp-54h]
006608DD call 00660490
006608E2 mov eax,dword ptr [ebp-54h]
006608E5 mov dword ptr [ebp-44h],eax
BaseClass.DoStuff();
006608E8 mov ecx,dword ptr [ebp-44h]
006608EB mov eax,dword ptr [ecx]
006608ED mov eax,dword ptr [eax+28h]
006608F0 call dword ptr [eax+10h]
006608F3 nop
}
0066091A nop
0066091B lea esp,[ebp-0Ch]
0066091E pop ebx
0066091F pop esi
00660920 pop edi
00660921 pop ebp
00660922 ret