2012-09-29 26 views
1

我的问题涉及在C++中对函数包装器进行内联优化的应用,请考虑以下代码,WorkerStructure对象使用封装了某些功能块的函数包装器进行初始化。然后在调用WorkerStructure :: doSomeWork方法时使用函数包装器。限制在C++中内联函数包装器

将当在WorkerStructure :: doSomeWork法涂布由workerFunction对象封装的功能性被内联?,很明显,如果该功能是在其它一些翻译单元所定义的,workerFunction对象只封装了一个函数指针,是否有任何其他情况下内联将不可能?

当在不同的翻译单元中定义的lambda函数通过函数包装器传递时,它是否等价于传递函数指针?

struct WorkerStructure 
{ 
    WorkerStructure(std::function <bool(float)> &f):workerFunction(f) {} 

    void doSomeWork(float inputValue) 
    { 
     if(workerFunction(inputValue)) 
     { 
      //do some conditional operation 
     } 
    } 
    std::function <bool(float)> workerFunction ; 
}; 
+2

它强烈依赖于编译器和优化标志。 FYI最近GCC(即4.7)具有链接时优化能力(编译**和** *链接*与'gcc -flto -O2') –

+0

Cool!,不知道flto,我正在试验很多用lambda函数,我非常喜欢他们给我的灵活性,但我不喜欢接受任何性能下降,因此是个问题。 –

+0

我最近把代码中的大部分代码转换成了在gcc 4.7.0中使用lambdas而没有遇到性能问题(我基本上用for_each_obj替换了一些自我创建的迭代器类对象来访问复杂的数据结构(lambda)',其中'for_each_obj'模板由数据结构提供,允许更好地优化循环逻辑)。 – Walter

回答

5

std::function多态本质上是使得非常非常很难真正内联调用。由于std::function可以讲述任何可调用实体;你会如何编写内联代码?

它有点像内联虚函数,它通过基指针调用,没有其他可用的信息(也就是说,在调用之前没有从派生指针到基指针的赋值,编译器可能用于启用内联)。

大多数时候,std::functionvoid*指针和函数指针的模板函数的专业化,执行实际invokation和铸造之类的东西来实现。当然有些变体使用虚拟功能来做到这一点,而且它更清楚为什么它很难。即使链接时间最优化也无法做任何事情,因为它无关紧要,您已经拥有了可以在呼叫站点获得的所有信息(这并不多)。

下面是一个使用指针模板功能版本的std::function一个非常原油的版本,只有存储和调用方面的处理(离开了内存管理,复制,移动,重设,空间优化等):

template<class Sig> 
class function; 

template<class R, class... Args> 
class function<R(Args...)>{ 
    typedef R (*call_type)(void*, Args...); 
    void* _obj; 
    call_type _caller; 

public: 
    template<class F> 
    function(F f) 
    : _obj(new F(f)) 
    , _caller([](void* p, Args... args){ return (*static_cast<F*>(p))(args...); }) 
    {} 

    R operator()(Args... args) const{ 
    return _caller(_obj, args...); 
    } 
}; 

Live example.我认为很难检查_obj_caller的实际内部以及function的调用点。

仅供参考,here's the version with virtual functions

+0

令人高兴的是,通过传递lambda作为模板参数,内联函数被正确appiled,我通过目测优化的反汇编对象代码来验证这一点。 –

+0

@Gearoid:哦,那很酷。 :)我想只有在你存储内容的同一范围内调用'std :: function'时,它才有效。 – Xeo

1

有趣的是,我问了关于刚刚在the mailing list上Clang/LLVM中虚函数的内联。 std::function的动态性质使得它本质上是一个虚拟呼叫,因为virtual调用并不比指向函数的指针多得多。

使用LLVM作为一个例子,让我们用下面的程序玩法:

#include <cstdio> 

typedef void (*Function)(); 

void donothing() {} 
void print() { printf("Hello World!"); } 

Function get(int i) { 
    if (i % 2 == 0) { return donothing; } 
    return print; 
} 

int main() { 
    Function f = get(0); 
    f(); 
} 

排放的主要功能:

define i32 @main() uwtable readnone { 
    ret i32 0 
} 

因此,编译器必须了解哪些功能被选中的能力(内联和不断传播的组合),并且确实将内联内联。

不幸的是,我在我的电子邮件中演示了通过虚拟表这不起作用(优化器以某种方式丢失了信息并且无法内联该呼叫)。所以虽然内联确实可以通过std::function工作,但它可能很好地依赖于编译器,但也取决于您碰巧使用的std::function的特定实现。我担心你需要试验你的应用程序。