【问题标题】:Is it worth it to avoid polymorphism in order to gain performance?为了获得性能而避免多态性是否值得?
【发布时间】:2014-01-16 01:53:39
【问题描述】:

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

我在 Stroustrup 的书中读到多态函数调用在 25% 以内 比普通的函数调用更昂贵。这是否意味着我应该 尽可能避免多态性?我的意思是我应该一直在想 类似的东西:好的多态性可以解决这个问题,但它可能会搞砸 提高性能 bla bla ... 或者考虑到现代 CPU 的强大功能,我什至不应该 敢这么想吗?

【问题讨论】:

  • 只要您不需要超低延迟,函数调用的成本可以忽略不计
  • @D.Shawley:这个问题是关于虚函数,而不是虚继承——完全不同的东西。
  • @user3111311 - 不要养成微优化的习惯。你最终付出了很多努力,却收效甚微。使用分析工具找出速度慢的地方。记住 80-20 规则。少走 25% 也没什么好担心的。
  • 为什么作为关于虚拟继承的问题有 3 票可以结束这个问题?如果您不知道虚拟调用和虚拟继承之间的区别,那么您不应该在 C++ 问题中投票。
  • 你知道虚函数调用不一定是虚函数调用吗?具有足够信息以了解确切对象类型的现代编译器将在编译时解析调用。这甚至意味着它可以随后被内联。此外,编译器写入可以优化虚拟调用机制。他们知道这是一种常用的机制。您自己的替代代码不会从此类特定优化中受益。

标签: c++ polymorphism


【解决方案1】:

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

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

最重要的是,虚函数调用必须通过函数指针进行调度,而函数指针意味着没有内联或任何形式的跨上下文优化。这通常是大多数减速的原因,即,您不应该将“虚拟函数调用”与“函数调用”进行比较,而应将“虚拟函数调用”与“静态可分析的、潜在的内联”进行比较能够、可优化的函数调用”。

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

在 C++ 和一般编程中,只要有价格,就会有收益,而只要有收益,就会有价格。就是这么简单。动态多态性的代价是(1)函数调用的开销,(2)阻塞静态分析和优化,(3)通常需要堆分配的对象和额外的间接(指针、引用等),等等上,仅举几例。但是,您还可以通过 (1) 运行时灵活性、(2) 可伸缩性(问题域的不相交联合)、(3) 减少编译时间、(4) 减少代码重复等形式获得实质性收益。

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

有关替代方案和权衡的更多详细信息,我建议this article

【讨论】:

    【解决方案2】:

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

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

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

    总体而言,您应该努力使您的代码易于理解。如果添加带有虚函数的子类可以更容易地理解您的设计,那么无论如何您都应该使用子类化。运行代码的硬件每年都在变得更快,因此从长远来看,可读性胜出。

    【讨论】:

      【解决方案3】:

      在“大多数”情况下,虚拟表查找开销不是问题。话虽如此,如果效率是要求:

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

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

      【讨论】:

        【解决方案4】:

        You Aren't Gonna Need It 是在这里应用的经验法则。

        任何一个的多态性实例都不可能让你的程序慢到值得担心的程度,所以当它是即时 情况。然而,如果你把所有东西都做成多态的,因为它可能以后会有用,那么你的程序减慢到足以让你担心的地步,因为现在 25%(或者它实际上是什么) ) 惩罚适用于每个函数调用Amdahl's law 不再是你的朋友。

        此外,从具有太多多态性的程序中删除多态性要比在没有多态性的程序中添加多态性要困难得多足够。添加多态性是局部更改。删除它涉及全局搜索,以确保您获得所有实现。

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

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

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

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

            【讨论】:

              猜你喜欢
              • 2016-12-22
              • 1970-01-01
              • 2014-03-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-07-07
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多