为什么预先指定的函数指针比分支表现更差?

时间:2012-07-25 14:28:22

标签: c++ performance optimization function-pointers

我有一个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的数组。我遍历数组以将其带入缓存,然后我调用cat update方法32次并使用rdtsc在数组中存储延迟...

然后我调用一个函数,它使用rand()ulseep()和一些任意strcmp()循环几百次..然后我再次执行32这个事情。

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

如果有的话,我会期待类似的表现。此外,模板化几乎不是一种选择,因为对于那一个函数的真实cat类的完全特化将是过度的。

1 个答案:

答案 0 :(得分:4)

如果没有完整的SSCCE,我就无法像通常那样对待这些问题。
所以我能做的最好就是推测:

两种情况的核心区别在于你有一个分支与一个函数指针。事实上,您发现funcA()funcB()功能非常小的所有强烈提示都存在差异。

可能性#1:

在代码的分支版本中,编译器会内联funcA()funcB()。这不仅会跳过函数调用开销,但如果函数足够微小,那么分支也可以完全优化。

另一方面,除非编译器可以在编译时解析它们,否则无法内联函数指针。

可能性#2:

通过将分支与函数指针进行比较,您将branch-predictor放在branch target predictor上。

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

处理器的分支预测器很可能比其分支目标预测器更准确。但话说回来,这都是猜测......