【问题标题】:Forcing inlining of callback (lambda) in C++17 in library在库中的 C++17 中强制内联回调 (lambda)
【发布时间】:2019-05-14 01:38:34
【问题描述】:

我正在构建一个运行时系统,允许程序员指定在特定点调用的回调。我正在使用 clang 7.0.1 / -std=c++17。通过将 lambda 存储为 std::function 在运行时注册回调。当运行时稍后调用std::function 回调时,它会传递 6 个参数(考虑到运行时的一般性,这是必要的)。请注意,std::function 是在应用程序中创建的,但由单独编译的静态链接库使用。但是,我正在使用 LTO(通过 -flto 和 LLD 7.0.1),所以我希望它仍然能够进行这种优化。我对其中一些东西不熟悉,所以希望这是可能的。

当我使用-O3 编译并在调用函数声明中指定__attribute__((flatten)) 时,lambda 未内联。当我使用 perf 事件运行我的系统时,我可以看到该函数没有被内联:

return _M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...);
 mov    -0x90(%rbp),%rdi  
 lea    -0x48(%rbp),%rsi  
 mov    %rbx,%rdx         
 mov    %r15,%rbx         
 callq  *0x180(%r15)      
...

这个调用花费了大量的时间,看起来应该是可内联的;总共只有几个呼叫站点。我之前确实见过内联的 lambda,但我不确定我使用仿函数的方法(通过 std::function)是否会以某种方式取消内联。

是否可以强制内联?如果需要更多信息,请告诉我。

编辑: 感谢所有非常有用的信息。我现在意识到我设置运行时的方式并没有让编译器有机会内联回调。 cmets 清楚地说明了为什么会这样。有一些暗示可能是内联的替代方法。鉴于 1)我同时控制应用程序和运行时源(以及编程模型/API); 2)我同时编译库和应用程序(甚至可以使它们成为一个统一的构建过程),我可以在这里采用可能允许内联发生的替代方法吗?也许是模板和 lambdas(不是std::functions)?我是这个领域的新手,如果有人对如何有效地为编译器提供内联所需的内容有想法,我会全力以赴。在最坏的情况下,我什至可以为每个应用程序构建一个自定义版本的库(作为概念证明),如果这会带来任何可能性......

【问题讨论】:

  • "看起来应该是可内联的" 你怎么看?您的描述强烈表明 lambda 的定义点和恰好包含该 lambda 的 std::function 对象的最终调用点相距甚远(如在不同的翻译单元中)。那么为什么你会期望内联呢?链接时优化不是魔法;它只能做这么多。
  • 你有没有测量过这是否是你的瓶颈?
  • 编译器使注册的回调可内联的唯一方法是证明它是唯一将被注册的回调。 LTO 通常无法做到这一点。它不是一种全程序优化技术。
  • @Swordfish 我看到使用 perf record -e Cycles:pp 初始化和调用函子是我的关键路径,也是我现在可以优化的前三件事之一

标签: c++ lambda inlining


【解决方案1】:

std::function 的全部意义在于拥有一个通用类型,该类型可以为特定签名保存任意可调用对象,同时允许通过通用接口调用任意可调用对象,无论是什么样的事情可调用的实际上恰好是。因此,如果您考虑一下,std::function 本质上需要某种间接方式。调用std::function 需要运行哪些代码不仅取决于类型,还取决于std::function 的特定值。这使得std::function(至少是对存储的可调用对象的调用)本质上是不可内联的。为调用您的回调的函数生成的代码必须能够处理您可能抛出的任何std::function。编译器可能为std::function 提供类似内联的唯一方法是,如果它能够以某种方式找出调用回调的函数大部分时间只会与具有特定值的std::function 对象一起使用然后为该特定情况生成调用回调的函数的克隆。这要么需要一个几乎不切实际的透视编译器才能实现,要么需要大量的魔法硬连线到编译器中,专门针对std::function。理论上也不是完全不可能。但我从未见过任何编译器实际上能够做这样的事情。根据我的经验,优化器并不能真正看穿std::function。而且我不希望这种情况很快改变,因为在那里进行任何有意义的优化似乎需要大量的努力才能获得相当可疑的好处。 std::function 一开始只是重型机械。您只需为在那里使用的东西付费。如果你不能付出代价,就不要使用std::function...

【讨论】:

  • std::function 在这里几乎不相关。使用普通的旧函数指针也可以内联,这也是非常不合适的。间接是可注册回调系统中固有的。
  • @n.m.我不得不不同意 std::function 在这里几乎不相关。问题是专门询问std::function。虽然您所说的在std::function 中调用可调用对象类似于仅通过普通函数指针调用函数是绝对正确的,但我认为这对每个人来说都不一定显而易见。至少,鉴于我最近在这个网站上看到了多少次使用std::function 将 lambdas 传递给函数模板以便立即调用它们的示例,我不得不得出结论,这显然不是显而易见的......
  • …我的印象是肯定有一些被误导的,但不幸的是,流行的来源正在教授 std::function 作为传递 lambdas 的默认方式…
  • std::function 作为传递 lambdas 的默认方式 这真的没有错。当然,除非您正在编写模板。
  • @n.m.确切地。然而,我只看到了如何从 lambda 参数推导出 std::function 的模板参数的问题的变体,这样给定的模板可能在过去一个月内多次调用 lambda,以至于我数不清了……出于某种原因,似乎人们认为std::function 是一个神奇的 lambda 容器,而不是一个多态函数对象……
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多