如果我使用 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_lock
、unqiue_lock
等,编译器非常擅长优化东西
Lambda 通常没有性能问题; std::function<void()>
会。
分析您的代码,启用所有优化。剖析是了解的唯一方法。没有分析,你只是在猜测。
在什么操作系统、编译器和平台上?
@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<void()> 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 块,是不是会有性能损失?的主要内容,如果未能解决你的问题,请参考以下文章