C# 为什么密封类型更快?
为什么密封类型更快C# 为什么密封类型更快?,c#,.net,performance,clr,C#,.net,Performance,Clr,为什么密封类型更快 我想知道关于为什么这是真的更深入的细节。本质上,这与他们不必担心虚拟函数表的扩展有关;密封类型不能扩展,因此,运行时不需要关心它们的多态性。如果JIT编译器看到使用密封类型调用虚拟方法,它可以通过非虚拟调用该方法来生成更高效的代码。现在调用非虚拟方法更快,因为不需要执行查找。IMHO这是一种微观优化,应该作为提高应用程序性能的最后手段。如果您的方法包含任何代码,与执行代码本身的成本相比,虚拟版本的速度将明显低于非虚拟版本。为了扩展其他人的答案,不能扩展密封类(相当于Java中
我想知道关于为什么这是真的更深入的细节。本质上,这与他们不必担心虚拟函数表的扩展有关;密封类型不能扩展,因此,运行时不需要关心它们的多态性。如果JIT编译器看到使用密封类型调用虚拟方法,它可以通过非虚拟调用该方法来生成更高效的代码。现在调用非虚拟方法更快,因为不需要执行查找。IMHO这是一种微观优化,应该作为提高应用程序性能的最后手段。如果您的方法包含任何代码,与执行代码本身的成本相比,虚拟版本的速度将明显低于非虚拟版本。为了扩展其他人的答案,不能扩展密封类(相当于Java中的最终类)。这意味着,每当编译器看到此类的方法被使用时,编译器绝对知道不需要运行时调度。它不必检查类来动态查看需要调用层次结构中哪个类的哪个方法。这意味着分支可以在中编译,而不是动态编译 例如,如果我有一个非密封类
Animal
,它有一个方法makeNoise()
,编译器不一定知道是否有任何Animal
实例重写了该方法。因此,每次任何Animal
实例调用makeNoise()
,都需要检查实例的类层次结构,以查看实例是否在扩展类中重写此方法
但是,如果我有一个密封类AnimalFeeder
,它有一个方法feedAnimal()
,那么编译器肯定知道这个方法不能被重写。它可以在分支中编译到子例程或等效指令,而不是使用虚拟调度表
注意:您可以在类上使用
sealed
,以防止从该类继承,也可以在基类中声明为virtual
的方法上使用sealed
,以防止进一步重写该方法。在最低级别,当您密封了类时,编译器可以进行微优化
如果在密封类上调用方法,并且类型在编译时声明为该密封类,则编译器可以使用call IL指令而不是callvirt IL指令来实现方法调用(在大多数情况下)。这是因为无法重写方法目标。调用消除了空检查,并且比callvirt执行更快的vtable查找,因为它不必检查虚拟表
这可能是一个非常非常轻微的性能改进
话虽如此,在决定是否封闭一个类时,我会完全忽略这一点。标记一个密封的类型实际上应该是一个设计决策,而不是性能决策。您希望人们(包括您自己)现在或将来潜在地从您的类中派生子类吗?如果是,不要密封。如果没有,请盖章。这确实应该是决定因素。决定发布一些小的代码示例来说明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()”方法:
看看“Call()”方法,我们可以说(理论上)C#编译器应该发出2个“callvirt”和1个“Call”指令,对吗?
不幸的是,现实有点不同-3“callvirt”-s:
原因很简单:在调用“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
}
如果您尝试以相同的方式调用非密封类的非虚方法,您也将得到“call”指令而不是“callvirt”要真正看到它们,您需要分析JIT编译的code(最后一个) C#代码
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();
}
.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
故障指示灯代码
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();
}
.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
JIT-编译代码
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();
}
.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
虽然对象的创建是相同的,但为调用密封类和派生类/基类的方法而执行的指令略有不同。将数据移动到寄存器或RAM(mov指令)后,调用密封方法,执行dword ptr[ecx]和ecx(cmp指令)之间的比较,然后调用该方法,而派生类/基类直接执行该方法
根据Torbj–orn Granlund撰写的报告《AMD和英特尔x86处理器的指令延迟和吞吐量》,英特尔奔腾4中以下指令的速度为:
- mov:延迟为1个周期,处理器每周期可支持2.5条此类指令
- cmp:延迟为1个周期,处理器每周期可支持2条指令
编译器的优化使得密封类和非密封类的性能差别如此之小,以至于我们谈论的是处理器圈,因此与大多数应用程序无关。请参见:是吗?我不知道。。。CLR可能能够优化方法分派表,知道它不能再增长了。@harpo:请参阅此引用:。我把它添加到我的答案中,但简单的事实是,他们没有说太多为什么,所以我决定不添加它…为什么这应该是最后的手段?为什么?