articleRelatedRecommend -1659782404.6767recentLists -1659782404.68article2MainLinks -1659782404.6816密封课程真的可以带来绩效收益吗?-IGI

密封课程真的可以带来绩效收益吗?

时间:2020-03-05 18:37:42  来源:igfitidea点击:

我遇到了很多优化技巧,这些技巧说我们应该将类​​标记为已密封,以获得更多的性能优势。

我进行了一些测试以检查性能差异,但没有发现。难道我做错了什么?我是否错过了密封类会带来更好结果的情况?

有没有人进行测试并发现差异?

帮我学习:)

解决方案

回答

JITter有时会在密封类中使用对方法的非虚拟调用,因为无法进一步扩展它们。

关于调用类型,虚拟/非虚拟类型有复杂的规则,我不了解它们,因此我无法真正为我们概述它们,但是如果我们搜索密封类和虚拟方法,则可能会找到有关该主题的文章。

请注意,我们将从此优化级别获得的任何类型的性能收益都应视为最后解决方案,始终在算法级别进行优化,然后再在代码级别进行优化。

这里是一个提到此的链接:在密封关键字上乱逛

回答

密封的类应可提高性能。由于无法派生密封类,因此任何虚拟成员都可以变成非虚拟成员。

当然,我们说的是很小的收获。我不会将一个类标记为密封的,只是为了获得性能改进,除非剖析显示它是一个问题。

回答

@Vaibhav,我们执行了哪种测试来衡量性能?

我猜想人们将不得不使用Rotor并深入研究CLI并了解密封类如何提高性能。

SSCLI (Rotor)

  SSCLI: Shared Source Common Language Infrastructure
  
  The Common Language Infrastructure
  (CLI) is the ECMA standard that
  describes the core of the .NET
  Framework. The Shared Source CLI
  (SSCLI), also known as Rotor, is a
  compressed archive of the source code
  to a working implementation of the
  ECMA CLI and the ECMA C# language
  specification, technologies at the
  heart of Microsoft’s .NET
  architecture.

回答

<题外话>

我讨厌密封班。即使性能优势惊人(我对此表示怀疑),它们也会通过防止通过继承进行重用而破坏面向对象的模型。例如,Thread类是密封的。虽然我可以看到人们可能希望线程尽可能地高效,但我也可以想象能够将Thread子类化的巨大好处。
类作者,如果我们出于"性能"的原因必须密封类,请至少提供一个接口,这样我们就不必在所有我们需要我们忘记的功能的地方包装和替换。

示例:SafeThread必须包装Thread类,因为Thread是密封的,并且没有IThread接口。 SafeThread会自动捕获线程上未处理的异常,这些异常在Thread类中完全丢失。 [并且不,未处理的异常事件不会在辅助线程中拾取未处理的异常]。

</ off-topic-rant>

回答

答案是否定的,密封类的性能不会比非密封类好。

问题归结于callcallvirtIL操作码。 Call的速度比callvirt的速度快,而callvirt则主要在我们不知道对象是否已被子类化时使用。因此人们认为,如果我们密封一个班级,那么所有操作码都将从" calvirts"更改为" calls",并且速度更快。

不幸的是,callvirt还会做其他使它有用的事情,例如检查空引用。这意味着即使一个类是密封的,引用也可能仍然为空,因此需要一个" callvirt"。我们可以解决这个问题(而无需密封类),但是它变得毫无意义。

结构使用call是因为它们不能被子类化并且永远不会为null。

有关更多信息,请参见此问题:

呼叫和呼唤

回答

将类标记为"密封"应该不会对性能产生影响。

在某些情况下,csc可能必须发出callvirt操作码而不是call操作码。但是,这些情况似乎很少见。

在我看来,如果JIT知道类还没有任何子类(尚未),它应该能够发出与对call相同的非虚拟函数调用callvirt。如果仅存在该方法的一个实现,则从vtable加载其地址就毫无意义,只需直接调用一个实现即可。为此,JIT甚至可以内联功能。

JIT方面有些赌博,因为如果以后加载子类,则JIT将不得不丢弃该机器代码并再次编译该代码,从而发出真实的虚拟调用。我的猜测是,这种情况在实践中并不经常发生。

(是的,VM设计人员确实确实在积极追求这些微小的性能优势。)

回答

运行以下代码,我们将发现密封类的速度提高了2倍:

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new SealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("Sealed class : {0}", watch.Elapsed.ToString());

        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new NonSealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("NonSealed class : {0}", watch.Elapsed.ToString());

        Console.ReadKey();
    }
}

sealed class SealedClass
{
    public string GetName()
    {
        return "SealedClass";
    }
}

class NonSealedClass
{
    public string GetName()
    {
        return "NonSealedClass";
    }
}

输出:
密封课:00:00:00.1897568
非密封班:00:00:00.3826678

回答

我认为"密封"类是正常情况,我总是有理由省略"密封"关键字。

对我而言,最重要的原因是:

a)更好的编译时检查(在编译时,不仅在运行时,将检测到未实现的接口的广播)

并且,最重要的原因:

b)这样不可能滥用我的课程

我希望微软能够使"密封"成为标准,而不是"未密封"。