为了获得性能而避免多态性值得吗

Is it worth it to avoid polymorphism in order to gain performance?

本文关键字:多态性 值得 性能      更新时间:2023-10-16

为了获得性能而避免多态性值得吗?

我在Stroustrup的书中读到多态函数调用在25%以内比普通函数调用更昂贵。这是否意味着我应该尽可能避免多态性?我是说我应该一直在想类似于:OK多态性可以解决这个问题,但它可能会一团糟上表演布拉布拉。。。或者考虑到现代CPU的强大,我甚至不应该敢这么想吗?

我在Stroustrup的书中读到多态函数调用比普通函数调用贵25%以内。这是否意味着我应该尽可能避免多态性?

不,绝对不是!非虚拟调用速度极快,因为调用本身通常是一条指令。虚拟调用需要额外的间接级别;这就是所谓的25%的来源,但这是"纯粹的开销",即当比较两个无参数函数的调用时所测量的开销。一旦开始添加参数,尤其是需要复制的参数,这两种开销之间的差距就会缩小:例如,按值将字符串传递给虚拟函数和非虚拟函数会使这种差距很难测量。

此外,只有调用指令本身更昂贵,而不是函数。调用函数的开销只占函数总时间的一小部分。函数越长,调用开销所代表的百分比就越小。

总的来说,您应该努力提高代码的可理解性。如果添加一个带有虚拟函数的子类可以更容易地理解您的设计,那么无论如何您都应该使用子类化。运行代码的硬件每年都会变得更快,所以从长远来看,可读性是优势。

我在Stroustrup的书中读到多态函数调用比普通函数调用贵25%以内。

可能是这样(可能是一个球场数字),但这并不重要。即使虚拟函数调用比普通函数调用贵25%,这仍然只是函数调用的成本,而不是函数执行的成本。我只是想做到这一点。函数调用的成本通常比函数执行的成本小得多。但请注意,它也并不总是微不足道的,如果你滥用多态性,并将其用于所有函数,甚至是最琐碎的函数,那么额外的开销可能会很快增加。

然而,最重要的是,虚拟函数调用必须通过函数指针进行调度,而函数指针意味着没有内联或任何形式的跨上下文优化。这通常是大部分放缓的原因,即,不应将"虚拟函数调用"与"函数调用"进行比较,而应将"虚函数调用"比作"可静态分析、可能内联、可lto优化的函数调用"。

为了获得性能而避免多态性值得吗?

在C++和一般编程中,只要有价格就有收益,只要有收益就有价格。就这么简单。动态多态性的代价是(1)函数调用的开销,(2)阻止静态分析和优化,(3)经常需要堆分配的对象和额外的间接性(指针、引用等),等等。然而,您也可以在以下方面获得实质性的好处:(1)运行时的灵活性,(2)可扩展性(问题域的不相交联合),(3)减少编译时间,(4)减少代码重复,等等

关键是,如果你想要多态性为你购买的东西,那么你应该使用它。但如果你不需要这些东西(你会惊讶于你不需要那些东西的频率),那么你不应该为此付出不必要的代价。就这么简单。

关于替代方案和权衡的更多细节,我建议这篇文章。

你不需要它是应用的经验法则,在这里。

多态性的任何一个实例都不太可能使您的程序慢到值得担心的程度,因此当多态性是即时情况的正确工具时,您应该毫不犹豫地使用它。然而,如果你让所有东西都是多态的,因为以后可能会有用,那么你的程序就会慢到令人担忧的地步,因为现在25%(或实际情况)的惩罚适用于每个函数调用,Amdahl定律不再是你的朋友。

此外,从一个有太多多态性的程序中删除多态性要比向一个没有足够多态性的节目中添加要困难得多。添加多态性是一个局部变化。删除它需要全局搜索,以确保获得所有实现。

它比普通函数调用更昂贵,但它有自己的优势。多态性是所有OO语言中最强大的东西之一。使用多态性,您可以相当容易地实现几种设计模式,并节省大量时间。它使代码保持干净、优雅、可重用、易于扩展和维护。这是最大的优势。因此,这确实是一种权衡。

在"大多数"情况下,虚拟表查找开销不是问题。也就是说,如果效率是一个要求:

  • 对于类似访问者模式的东西,您也许可以使用模板化的解决方案,而不是基于虚拟函数的解决方案
  • 还应该考虑静态多态性,使用奇怪的递归模板模式

请记住,只有当您在"敏感"系统(如日志记录或监控接口)或需要重复调用不同回调的算法(如a*或行为树)上工作时,这一点才很重要。

与许多关于性能/效率的问题一样,您通常必须根据具体情况进行评估。它可以在运行时性能和一般编程问题(如多功能性和可维护性)之间进行权衡。

话虽如此,基于假设的性能问题来避免这样一个主要的语言功能可能是个坏主意。任何单个函数调用(即使是间接调用)的影响都很小。与其他典型的瓶颈相比,你必须非常频繁地调用它才能产生重大影响。

如果存在与特定函数调用相关的重大性能问题,那么(根据我的经验)对算法的其他调整往往更有用。例如,预取或缓存数据,或者寻找其他方法来减少调用频率。