我正在做一些计时测试,其中一项测试是比较调用函数的不同方式。我使用各种方法调用了 N 个函数。我尝试了常规函数调用、虚函数调用、函数指针和 boost::function。
我在 Linux 中使用 gcc 和 -O3 优化来做到这一点。
正如预期的那样,虚拟调用比常规函数调用慢。然而,令人惊讶的是 boost::function 比虚拟调用慢了 33%。
有没有其他人注意到这一点?任何线索为什么会这样?
我正在做一些计时测试,其中一项测试是比较调用函数的不同方式。我使用各种方法调用了 N 个函数。我尝试了常规函数调用、虚函数调用、函数指针和 boost::function。
我在 Linux 中使用 gcc 和 -O3 优化来做到这一点。
正如预期的那样,虚拟调用比常规函数调用慢。然而,令人惊讶的是 boost::function 比虚拟调用慢了 33%。
有没有其他人注意到这一点?任何线索为什么会这样?
如果可能,编译器可以内联常规函数,但boost::function
永远不能内联。这是一大区别。
第二个区别是,boost::function
实现类型擦除,这意味着它使用间接调用实际函数。意味着它首先调用一个虚函数,然后调用你的函数。所以通常它涉及(最少)两个函数调用(其中一个是virtual
)。那是巨大的差异。
因此,基于此分析,可以推断出这一点(甚至无需编写测试代码):
slowest ------------------------------------------------------> fastest
boost::function < virtual function < regular function
slowest ------------------------------------------------------> fastest
在您的测试代码中确实如此。
请注意,它也适用std::function
(自 C++11 起可用)。
Aboost::function
不仅可以保存函数指针,还可以保存它调用可能虚拟对象的任意对象的完整副本operator()
。
它可以帮助理解它是如何工作的(用于说明)。
这是一个boost::function
类型技巧的玩具实现:
struct helper_base { virtual void do_it() = 0; };
template<typename Func>
struct helper:helper_base {
Func func;
helper(Func f):func(f) {}
virtual void do_it() override { func(); }
};
struct do_something_later {
boost::unique_ptr<helper_base> pImpl;
template<typename Func>
do_something_later( Func f ):pImpl(make_shared<helper<Func>>(f))
{}
void operator()() { (*pImpl).do_it(); }
private:
do_something_later( do_something_later const& ); // deleted
void operator=( do_something_later const& ); // deleted
};
这里 mydo_something_later
接受一个任意对象 (Func) 并operator()
按需调用它。它将我们正在调用的事物的类型包装在operator()
类型擦除助手中,然后operator()
通过虚函数调用。
Func
type 可以是函数指针,也可以是带状态的函子。任何可以用 operator() 复制的东西都是公平的游戏。就用户do_something_later
而言,只有一个二进制接口。
boost::function
(and std::function
) 使用基本相同的技术(有很多改进)将一整套可能的接口转换为一个接口。成本涉及调用virtual
函数(或等效的间接级别)。
观察到的缓慢的真正原因是boost::function
除了两个间接将指针与零进行比较。如果省略了这个测试,那么调用的执行速度将与虚函数一样快(这也涉及两个间接——一个指向 vtable 的指针,另一个指向实际函数)。