为什么密封类型更快?

Joa*_*nge 35 .net c# clr performance

为什么密封类型更快?

我想知道为什么这是真的更深层次的细节.

Ree*_*sey 38

在最低级别,编译器可以在密封类时进行微优化.

如果要在密封类上调用方法,并且在编译时将类型声明为密封类,则编译器可以使用调用IL指令而不是callvirt IL指令来实现方法调用(在大多数情况下).这是因为无法覆盖方法目标.调用消除了空检查,并且比callvirt执行更快的vtable查找,因为它不必检查虚拟表.

这可以是对性能的非常非常小的改进.

话虽这么说,在决定是否密封课程时,我会完全忽略这一点.标记密封的类型确实应该是设计决策,而不是性能决策.您是否希望现在或将来人们(包括您自己)可能从您的班级继承?如果是这样,请不要密封.如果没有,请密封.这确实应该是决定因素.

  • 在设计时,倾向于密封不明确需要扩展的公共类型可能是一个好主意,因为在未来版本中开启类是一个非破坏性的变化,而反之则不然. (5认同)
  • @Neil Williams:我同意.一般来说,由于开封一个类是安全的,而密封不是,如果你正在建立公共图书馆,密封可能是一件好事.但同样,这使得密封设计选择不仅仅是性能问题. (2认同)

Pau*_*ier 10

从本质上讲,它与他们不必担心虚拟功能表的扩展这一事实有关; 密封类型不能扩展,因此,运行时不需要关心它们如何是多态的.


Vol*_*kyy 8

决定发布小代码示例,以说明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()
        { }
    }
Run Code Online (Sandbox Code Playgroud)

我还有一个方法调用所有"DoSmth()"方法:

    public void Call()
    {
        SealedClass sc = new SealedClass();
        sc.DoSmth();

        ClassWithVirtualMethod cwcm = new ClassWithVirtualMethod();
        cwcm.DoSmth();

        ClassWithSealedMethod cwsm = new ClassWithSealedMethod();
        cwsm.DoSmth();
    }
Run Code Online (Sandbox Code Playgroud)

看看"Call()"方法我们可以说(理论上)C#编译器应该发出2个"callvirt"和1个"call"指令,对吧?不幸的是,现实有点不同 - 3"callvirt"-s:

.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 
}
Run Code Online (Sandbox Code Playgroud)

原因很简单:运行时必须在调用"DoSmth()"方法之前检查类型实例是否不等于null. 我们仍然可以编写代码,使C#编译器能够发出优化的IL代码:

    public void Call()
    {
        new SealedClass().DoSmth();

        new ClassWithVirtualMethod().DoSmth();

        new ClassWithSealedMethod().DoSmth();
    }
Run Code Online (Sandbox Code Playgroud)

结果是:

.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 
}
Run Code Online (Sandbox Code Playgroud)

如果你尝试以同样的方式调用非密封类的非虚方法,你也会得到"call"指令而不是"callvirt"


Dar*_*rov 5

如果JIT编译器使用密封类型看到对虚方法的调用,则可以通过非虚拟地调用该方法来生成更高效的代码.现在调用非虚方法更快,因为不需要执行vtable查找.恕我直言,这是微优化,应该作为提高应用程序性能的最后手段.如果您的方法包含任何代码,则与执行代码本身的成本相比,虚拟版本将比非虚拟版本慢得多.

  • 为什么这是最后的手段呢?为什么不默认密封你的课程?如果与它相关的一些成本(代码可读性较差或通常更多的开发时间),通常只考虑微优化.如果做到这一点没有任何不利因素,那么无论性能是否成问题,为什么不这样做呢? (2认同)