【问题标题】:Why does a preassigned function pointer perform worse than a branch?为什么预分配函数指针的性能比分支差?
【发布时间】:2012-07-25 23:31:28
【问题描述】:

我有一个带有enum 成员变量的类。其中一个成员函数的行为基于此enum,因此作为“可能的”优化,我将两种不同的行为作为两个不同的函数,并且我给类一个在构造时设置的成员函数指针。我是这样模拟这种情况的:

enum catMode {MODE_A, MODE_B};

struct cat
{
    cat(catMode mode) : stamp_(0), mode_(mode) {}

    void
    update()
    {
        stamp_ = (mode_ == MODE_A) ? funcA() : funcB();
    }

    uint64_t stamp_;
    catMode  mode_;
};

struct cat2
{
    cat2(catMode mode) : stamp_(0), mode_(mode)
    {
        if (mode_ = MODE_A)
            func_ = funcA;
        else
            func_ = funcB;
    }

    void
    update()
    {
        stamp_ = func_();
    }

    uint64_t stamp_;
    catMode  mode_;
    uint64_t (*func_)(void);
};

然后我创建一个 cat 对象和一个长度为 32 的数组。我遍历数组将其放入缓存中,然后调用猫更新方法32 次并使用rdtsc 将延迟存储在数组中...

然后我使用rand()ulseep() 和一些任意的strcmp()..调用一个循环数百次的函数。回来我再做32 的事情。

结果是带有分支的方法似乎总是在44 +/- 10 周期附近,而带有函数指针的方法往往在130 附近。我很好奇为什么会这样?

如果有的话,我会期待类似的表现。此外,模板化几乎不是一种选择,因为真正的 cat 类针对该函数的完全专业化将是多余的。

【问题讨论】:

  • @sixlettervariables - 可能更多的是 CPU 具有分支预测,而函数指针调用涉及清除堆栈/寄存器。您不一定会从汇编中看到这一点
  • 取决于funcAfuncBs 的复杂性,它们可能已经被内联。我同意@sixlettervariables 的观点,即检查程序集是个好主意。这很容易做到,如果不出意外,至少你会排除一些可能的解释。
  • 当您使用模板结构来包装特定函数时,您可以避免完全专业化。那么你只需要特化结构体,猫类只需要指定一次。
  • 这是什么处理器?另外,您能否提供一个完整的 SSCCE,以便我自己运行?
  • @tokage 等等,这很有趣,我在哪里可以读到这个或它是如何工作的?

标签: c++ performance optimization function-pointers


【解决方案1】:

如果没有完整的 SSCCE,我无法像通常处理此类问题一样处理这个问题。
所以我能做的最好的就是推测:

您的两种情况之间的核心区别在于您有一个分支与一个函数指针。您看到差异的事实强烈暗示 funcA()funcB() 是非常小的函数。

可能性#1:

在代码的分支版本中,funcA()funcB() 正在被编译器内联。这不仅跳过了函数调用开销,而且如果函数足够简单,分支也可以完全优化。

另一方面,函数指针不能被内联,除非编译器可以在编译时解析它们。

可能性 #2:

通过将分支与函数指针进行比较,您可以将branch-predictorbranch target predictor 放在一起。

分支目标预测与分支预测不同。在分支情况下,处理器需要预测分支的方式。在函数指针的情况下,它需要预测分支到哪里。

您的处理器的分支预测器很可能比其分支目标预测器准确得多。不过话说回来,这都是猜测……

【讨论】:

  • 谢谢!我希望我以前见过这个。什么是完整的 SSCCE?是的 funcA 和 funcB 都比较小。它是一个 2 cpu intel Xeon,每个 cpu 有 6 个内核。
  • SSCCE = 简短的自包含可编译示例,所以基本上是一个小的 sn-p 可以重现您遇到的问题。
  • 有什么好的方法/资源来了解您的处理器的优点、延迟等?例如 Xeon 还是 Sandy Bridge?我认为这将有助于做出更好(尽管不可移植)的优化决策。
  • Agner Fog's blog 有一些非常好的资源。他掌握了当前大多数 x86 处理器的大量延迟表。但请注意,性能比仅仅增加延迟要复杂得多。所以你必须做大量的测试和实验。
猜你喜欢
  • 2021-02-14
  • 2014-12-02
  • 2018-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多