如果我使用 lambda 而不是 If 块,是不是会有性能损失?

Posted

技术标签:

【中文标题】如果我使用 lambda 而不是 If 块,是不是会有性能损失?【英文标题】:Is there preformance penalty if I use lambda instead of an If block?如果我使用 lambda 而不是 If 块,是否会有性能损失? 【发布时间】:2020-04-26 11:58:30 【问题描述】:

我正在使用Dear ImGui 在 OpenGL 程序中以即时模式绘制我的 UI。

要做一个标签,你通常这样做:

if (ImGui::BeginTabItem("Some Tab")) 
    // Stuff
    ImGui::EndTabItem();

我来自 Kotlin,你通常使用这样的结构:

something("bla bla bla")  x ->
  // whatever
 // this is a Kotlin lambda btw

所以我写了一个简单的包装器,将调用包装起来,而不是写 if 和 end 调用,以避免不小心忽略 end 调用:

inline void tab(const char* label, std::function<void()> fn)

    if (ImGui::BeginTabItem(label)) 
        fn();
        ImGui::EndTabItem();
    

用类似的方式替换第一个 sn-p:

ui::tab("Some other tab", []
    // More UI...
);

问题是,编译器会发出类似的代码吗?或者会有很大的性能影响?

恐怕如果编译器每次绘制 UI 时都放置一个新的可调用结构,那将是一个问题。

另外,我正在捕获this 指针,以便在内部使用。

if (ImGui::BeginTabItem("Some Tab")) 
    // Stuff
    ImGui::EndTabItem();


// -- VS -- //

ui::tab("Some other tab", [this]
    // More UI...
);

【问题讨论】:

我不会担心“每次绘制 UI 时都会出现新的可调用结构”。唯一目的是清理的类是一种常见的模式,例如scoped_lockunqiue_lock 等,编译器非常擅长优化东西 Lambda 通常没有性能问题; std::function&lt;void()&gt; 会。 分析您的代码,启用所有优化。剖析是了解的唯一方法。没有分析,你只是在猜测。 在什么操作系统、编译器和平台上? @BasileStarynkevitch g++ 9.2.0 (MSYS2),Windows 10 【参考方案1】:

如果我使用 lambda 而不是 If 块,会有性能损失吗?

这取决于您的编译器和optimization 标志。

将现代的GCC 调用为gcc -Wall -O3 -mtune=native,您会惊讶于它能够进行的优化,包括inline expansions。您甚至可能对链接时间优化和整个程序优化感兴趣(例如,使用gcc -O3 -flto -fwhole-program .... 编译和链接)。阅读 GCC optimization flags。

参见 this draft 报告,该报告由 CHARIOT H2020 项目资助。

当然,邪恶在于细节

您可以使用 plugin 扩展 GCC,从而改进更多优化。

但请注意,编译器优化理论上无法确定(请参阅λ-calculus、Rice's theorem、MRDP theorem、incompleteness theorems、Curry-Howard correspondence、@987654333 @、J.Pitrat's blog、RefPerSys 项目等)而且实际上并不完美(与其说是科学,不如说是一门艺术)。

当前高端处理器(CPU cache、branch predictors、superscalar architectures)的复杂性(和商业机密)使得对worst-case execution time 的分析几乎不可能。在实践中,您会遇到编译器优化会让您失望的情况。因此,请在 profiling 上努力(例如,在 Linux 上使用 gprof 或 perf)

另见Ctuning、CompCert 和Milepost GCC 项目。还要考虑OpenCL、OpenMP、OpenACC。

不要忘记资助和支持专门从事优化的研究团队,例如给我发邮件到basile@starynkevitch.net(但是预算要求超过10万欧元,延误一年多)。

最后,您可以(在许多平台上)在运行时生成特定代码(使用partial evaluation 技术)。然后考虑使用JIT compilation 库,例如libgccjit。在某些操作系统上,您可以在运行时生成 C++ 代码,然后编译它并将其加载为 plugin(例如使用 dlopen)。

当然要阅读 Thompson Reflections on Trusting Trust 论文和 Bjarne Stroustrup papers。

您当然可以考虑使用JNI 或SBCL 或LuaJIT。两者都可以在主要计算平台上与 C++ 混合使用,并且实际上促进了运行时代码的生成,因此通过努力可以提高执行时间。

你正在编码:

inline void tab(const char* label, std::function&lt;void()&gt; fn)

(如果fn 可能间接地是throw-ing 一些例外,那么您的代码不会执行您想要的操作)我建议改为

 inline void tab(const char* label, const std::function<void()>& fn)

【讨论】:

应该是-mtune=native 我看到这是一个相当困难的主题,我跑了一个快速板凳(我不知道设置是否正确):quick-bench.com/k6FBlha1RHT1blwrfY9iTp7aJGM 所以也许可以。我现在将使用 lambda,看看将来是否会成为问题。 PS:感谢资源!【参考方案2】:

您似乎不需要std::function 为您提供的全部功能(特别是它的运行时多态行为),所以最好不要使用它。

只需使用模板函数,这对于编译器来说更容易优化:

template <typename CALLABLE>
void tab(const char* label, CALLABLE &&fn)

    if (ImGui::BeginTabItem(label)) 
        std::forward<CALLABLE>(fn)();
        ImGui::EndTabItem();
    

我会在这里使用std::function 的唯一情况,如果您想避免模板版本所具有的这种小代码膨胀,并且您希望将tab 作为非内联函数。

原因是std::function 可能是一个更糟糕的解决方案,因为std::function 需要将 lambda(及其捕获的变量)存储在某处。堆通常用于此目的(除非它适合 std::function 的小对象优化空间 - 但现在您依赖于 std::function 的实现确实有这个小对象空间,并且编译器能够围绕这个进行优化)。所以只使用模板版本更容易,编译器非常擅长优化这一点,因为标准库大量使用了这种技术。

【讨论】:

感谢模板提示!我已切换到模板 const ref 以避免复制我传递的任何内容。 @SigmaSoldier:我的示例使用转发引用,它也不会复制。 真的!我也可以用这个

以上是关于如果我使用 lambda 而不是 If 块,是不是会有性能损失?的主要内容,如果未能解决你的问题,请参考以下文章

Rails Routes:带有身份验证块的更清晰的 if 语句

python的匿名函数

匿名函数lambda

为啥在 lambda 中使用 ++i 而不是 i++

何时使用函数模板而不是通用 lambda?

隐藏块而不是删除