2012-12-06 160 views
4

我正在做一些时间测试,我的一个测试是比较调用函数的不同方式。我用各种方法调用了N个函数。我尝试了常规函数调用,虚函数调用,函数指针和boost :: function。为什么boost :: function很慢?

我在Linux中使用gcc和-O3进行了优化。

如预期的那样,虚拟呼叫比常规函数调用慢。然而,令人惊讶的是,boost ::函数的速度比虚拟调用慢33%。

有没有其他人注意到了这一点?任何线索为什么这是?

+0

你确定你已经实现了多个虚拟函数变体吗?编译器可能有点棘手,如果他们确信只有一个类实际实现了虚函数,那么就可以推导出这种类型。 – ltjax

+0

我没有想到会发生这种情况,因为不同之处在于常规函数调用和虚函数调用很大足以让我感到满意的是,没有任何棘手的编译器优化发生。 –

+1

不回答你的问题,但如果你对性能墙壁上,你可能有兴趣在一个慢更少的选择: [较快代表] [1] [1]:HTTP:// www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible –

回答

6

常规功能可以被编译器内联如果可能的话,但boost::function可以永远被内联。这是一个很大的区别。

第二个区别是,boost::function implements type-erasure这意味着它使用间接来调用实际功能。意味着它首先调用一个虚函数,然后调用你的函数。所以通常它涉及(最少)两个函数调用(其中之一是virtual)。那就是巨大的的区别。

因此,基于这样的分析,我们可以推断出这一点(甚至没有编写测试代码):

slowest ------------------------------------------------------> fastest 
     boost::function < virtual function < regular function 
slowest ------------------------------------------------------> fastest 

这的确是这样的,在您的测试代码。

请注意,对于std::function也是如此(自C++ 11起可用)。

+0

并且调用'boost :: function'永远不会只是一个函数调用。 –

+0

是否也适用于C++ 11 lambda? – GreyGeek

+0

@GreyGeek不,因为lambdas不是'std :: function'。事实上,它们可以比C风格的函数指针更容易,因为调用的函数暴露在变量类型中。 – Yakk

2

A boost::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 
}; 

这里我do_something_later需要的任意对象(Func键)和按需调用它operator()。它将我们在类型擦除助手中调用operator()的东西包装起来,然后通过虚函数调用operator()

Func类型可能是一个函数指针,或者它可能是一个具有状态的函子。任何可与操作员()复制的东西都是公平的游戏。就do_something_later的用户而言,只有一个二进制接口。

boost::function(和std::function)基本上使用这种相同的技术(有很多改进)将一组可能的接口变成一个接口。成本涉及调用virtual函数(或间接的等效水平)。

+0

很好的解释。我仍然想知道为什么需要虚拟调度?为什么不跳过帮助器,直接在struct do_something_later中包含Func? –

+1

因为'do_something_later'必须在'Func'的类型上模板化。相反,只有构造函数必须。上面的代码为每个知道'Func'对象有多大的'Func'类型构建一个自定义'helper',并且知道'operator()'的签名是什么(它是虚拟的?它使用什么调用约定?它实际上是否返回'int',并且因此需要在堆栈上返回值的空间?它实际上是否返回'std :: vector ',因此我们需要清理'do_it()'中返回的对象? ) – Yakk

0

观察到的缓慢的真正原因是boost::function除了两个间接指针之外还将指针指向零。如果这个测试被省略了,那么这个调用的执行速度将与虚函数一样快(这也涉及到两个间接指针 - 一个指向vtable,另一个指向实际函数)。