简体   繁体   English

限制C ++中的内联函数包装器

[英]Limits to inlining function wrappers in C++

My question concerns the application of inline optimisations on function wrappers in C++, consider the following code, the WorkerStructure object is initialised with a function wrapper that encapsulates some chunk of functionality. 我的问题涉及内联优化在C ++中的函数包装上的应用,请考虑以下代码,使用封装一些功能块的函数包装初始化WorkerStructure对象。 The function wrapper is then used when the WorkerStructure::doSomeWork method is invoked. 然后,在调用WorkerStructure :: doSomeWork方法时使用函数包装器。

Will the functionality encapsulated by the workerFunction object be inlined when applied on the WorkerStructure::doSomeWork method?, obviously if the functionality is defined in some other translation unit, the workerFunction object only encapsulates a function pointer, are there any other circumstances where inlining will not be possible? 当将WorkerFunction对象封装的功能应用于WorkerStructure :: doSomeWork方法时,是否会内联?显然,如果该功能是在其他转换单元中定义的,workerFunction对象只会封装一个函数指针, 在任何其他情况下,内联都会不可能吗?

When a lambda function defined in a different translation unit is passed via the function wrapper, is it effectively equivalent to passing a function pointer? 通过函数包装器传递在另一个转换单元中定义的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 ;
};

The polymorphic nature of std::function inherently makes it very very difficult to actually inline the call. std::function的多态性质固有地使实际上内联调用非常困难。 Since a std::function can story any callable entity; 由于std::function可以说明任何可调用实体; how would you write the inlining code? 您将如何编写内联代码?

It's somewhat like inlining virtual functions which are called through the base pointer with no other information available (aka no assignment from derived to base pointer prior to the invokation, which the compiler might use to enable inlining). 这有点像内联虚拟函数,它们通过基本指针调用而没有其他可用信息(也就是在调用之前没有从派生到基本指针的赋值,编译器可能会使用这些赋值来启用内联)。

Most of the time, std::function is implemented with a void* pointer and a function pointer to a specialization of a templated function, that does the actual invokation and casting and stuff. 大多数情况下, std::function是通过void*指针和指向模板化函数特化的函数指针实现的,该模板化函数执行实际的调用,转换和填充。 There are of course variants that use virtual functions to do this, and it's clearer with them why it's astonishingly hard. 当然,有一些变体使用虚函数来完成此任务,并且它们之间的区别更加清楚为什么如此之难。 Even link-time opimization won't be able to do anything, since it doesn't matter, you already have all the information you can get at the call site (which isn't much). 甚至链接时间优化也将无能为力,因为这无关紧要,您已经拥有了可以在呼叫站点获得的所有信息(这并不多)。

Here's a very crude version of std::function using the pointer to template function version, dealing only with the store and call aspect (leaving out memory management, copying, moving, resetting, space optimization etc.): 这是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. 现场示例。 I think it'd be very hard to check what is actually inside of _obj and _caller and the point where the function is invoked. 我认为很难检查_obj_caller实际_obj以及调用该function位置。

Just for reference, here's the version with virtual functions . 仅供参考,以下是带有虚函数的版本

Amusingly, I asked about inlining of virtual functions in Clang/LLVM on the mailing list just today. 有趣的是,我刚刚在邮件列表上询问了Clang / LLVM中虚拟函数的内联。 The dynamic nature of std::function makes it essentially a virtual call, because virtual calls are not much more than pointer to functions. std::function的动态性质使其本质上是一个虚拟调用,因为virtual调用只不过是指向函数的指针。

Using LLVM as an example, let us play with the following program: 以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();
}

The main function emitted: 发出的主要功能:

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

Therefore, the compiler has the ability to understand which function gets selected (with a combination of inlining and constant propagation), and did inline the call. 因此,编译器能够了解选择了哪个函数(内联和常量传播的组合),并内联了调用。

Unfortunately, I demonstrated in my e-mail that passing though virtual tables this does not work (the optimizer somehow lost information and was not able to inline the call). 不幸的是,我在电子邮件中证明了通过虚拟表传递消息是行不通的(优化器以某种方式丢失了信息并且无法内联调用)。 So while it is perfectly possible that inlining does work through std::function , it may very well depend not only on the compiler but also on the particular implementation of std::function that you happen to be using. 因此,尽管内联确实有可能通过std::function ,但它很可能不仅取决于编译器,而且还取决于您恰巧使用的std::function的特定实现。 I am afraid that you will need experimenting with your application. 恐怕您将需要试验您的应用程序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM