4

我有一个带有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 类针对该功能的完全专业化将是矫枉过正的。

4

1 回答 1

4

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

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

可能性 #1:

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

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

可能性 #2:

通过将分支与函数指针进行比较,您将分支预测器分支目标预测器放在一起。

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

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

于 2012-07-25T15:31:28.000 回答