【问题标题】:have function calls a perceptible overhead in C++?函数调用在 C++ 中有可察觉的开销吗?
【发布时间】:2012-10-10 11:52:25
【问题描述】:

在我的项目中,我有一个以执行时间为首要目标的课程。对于它我不太关心维护,订单等。至少我直到昨天才在意……现在我也不得不有点担心。

我有一个类,比如 A,它对来自相机的图像执行多次扫描,即可变宽度窗口实时扫描它们。



    class A{  
    // methods and attributes of A:  
    ...  
    void runiterator(){  
      ...  
    for{    // change window’s dimension  
      for{  // rows  
       for{ // columns  
          // many lines of code of operations to be executed for each window at each position  
          ...      
       }  
      }   
     }  
    }  
    }; 

性能显示已经有一点延迟,但我可以跳过图像的有限区域来解决它。此外,我还有第二个函数,比如 B,它与 A 具有完全相同的方案,并且在每次扫描时执行不同的操作(幸运的是比 A 快得多)。

嗯,现在是时候加入所有操作以显着受益于整体结果。只是代码真的会变得混乱,庞大并且混合了真正不同的东西。我想定义一个执行迭代的类 X,并在每次扫描时执行对 A_new 中的一个函数和 B_new 中的一个函数的函数调用。但我担心每张图像大约 200000x2 次函数调用会导致性能下降。

你有什么建议?

编辑
对于只调用 Anew 的 X 类(所以它只能与现在的 A 进行比较),平均而言,我得到了多次重复:

在一系列 56 张图像上执行 X 的时间 = 6.15 秒
在同一系列的 56 张图像上执行 A 的时间 = 5.98 s

看来我的嫌疑人并没有那么天真。
差价大约是3%,不算多,但还是对损失感到抱歉。

X 的 __forceinline 时间也是 5.98 秒,但我不想依赖它。

我认为代码已经过优化,进一步改进的余地很小。
事实上,它在相对较短的时间内对图像做了很多事情。
在 A 类中按顺序处理数据是不可能的,因为它基于来自图像的不可预测的值。这就是为什么 B 级(设法做到这一点)要快得多的原因。

【问题讨论】:

  • 你能尝试和测量两个版本吗?我们不能真正说出调用增加了多少开销,因为它是相对的。
  • 是的,测量,让编译器有机会内联函数调用。
  • 这取决于这些函数的作用。
  • 如果您以执行时间为主要目标而牺牲了清晰度,那么您很可能会得到执行非常糟糕的代码。相反,将清晰度和可维护性作为您的首要任务。无论如何,您可能会以这种方式获得最佳性能。除非您比这个问题所暗示的更有经验,否则您对性能的直觉无论如何都会在很大程度上是错误的,并且您的“优化”代码将很脆弱,因此难以实际优化。 (例外是高级算法选择——这适用于低级和中级选择,比如函数边界应该在哪里。)
  • 如果算法太慢,尝试优化它!

标签: c++ performance function-call


【解决方案1】:

在担心它之前,您确实必须衡量它是否会导致性能问题。

如果存在问题,请尝试使用模板来解决。编写函数的两个变体,然后将它们用作执行迭代的函数模板中的函子。您将实例化这两个版本,并调用适当的版本。编译器应该内联调用(但最好验证这一点)。

我将它用于医学图像处理,效果非常好。

【讨论】:

    【解决方案2】:

    函数调用开销很大程度上取决于您调用的函数类型。在汇编程序级别(假设没有偷偷摸摸的操作系统页面错误处理)现代英特尔处理器上的call address 指令需要 0 个周期(jmp address 指令也是如此)。如果函数地址是从某个数据源计算的,例如 vtbl 查找、对外部 DLL 的调用(如果您使用的是 Win32)或涉及条件,则会引入开销。这些涉及访问内存和污染缓存。这让我们大吃一惊。

    在等待数据到达 CPU 时会损失大部分性能。 CPU 的速度远远大于从 RAM 中读取数据的速度。这就是为什么有多个缓存级别的原因,每个级别通常比前一个级别更大且更慢。一个函数调用的成本,即使是一个复杂的调用,也比在读取数据时丢失缓存所损失的时间要少。

    这类事情属于标题:微优化。

    一般情况下,避免随机数据访问,按顺序处理数据,即执行第 n 项,然后执行 n+1、n+2 等而不是 n、n+100、n+200 等,n+1、n+ 101、n+201等

    另外,让编译器有机会内联函数 - 如果结果产生更快的代码,则内联将完成(并且编译器非常清楚何时这样做是有益的)。

    还请注意,大函数可能比许多小函数慢(这与 CPU 本地缓存的 uop 缓冲区有关)。多次迭代数据可能比一次性完成所有事情要快。只有分析代码才能告诉您哪个更快。

    最后,更好的算法通常是提高性能的方法。你的算法是最优的吗?

    【讨论】:

      【解决方案3】:

      您必须测量效果,因为很难判断编译器真正产生了什么——尤其是在O3。当然,如果函数没有被编译器内联,函数调用就会产生开销。如果可以内联函数,请尝试向编译器提供inline 提示。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-07-30
        • 1970-01-01
        • 2015-10-21
        • 2011-11-09
        • 2013-08-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多