The C++ Standard Library (Second Edition)Nicolai Josuttis 在其着作中指出,编译器可以比普通函数更好地优化lambdas.
此外,C++编译器比普通函数更好地优化lambdas.(第213页)
这是为什么?
我认为在内联时不应该有任何差别.我能想到的唯一原因是编译器可能有一个更好的本地上下文与lambdas,这样可以做出更多假设并执行更多优化.
我想内联一个lambda表达式,因为它因性能原因而非常短.可能吗?
背景
作为一种组织策略,我喜欢在复杂的函数中定义函数局部 lambda。它适用于封装多步逻辑、重复操作等(一般来说,函数适用的类型),但不会创建在使用范围之外可见的东西。这是约翰卡马克在他的关于内联代码优点的文章中阐述的风格的综合/替代品,因为它使所有东西都整齐地封装在它打算使用的函数中,同时还提供了一个(编译器识别的)名称来记录每个功能块。一个简单的、人为的例子可能看起来像这样(假设这里实际上发生了一些足够复杂的事情,值得使用这种风格):
void printSomeNumbers(void)
{
const auto printNumber = [](auto number) {
std::cout << number << std::endl; // Non-trivial logic (maybe formatting) would go here
};
printNumber(1);
printNumber(2.0);
}
Run Code Online (Sandbox Code Playgroud)
从语义上讲,这个函数的编译形式“应该”创建一个隐式定义的函子的实例,然后operator()()为每个提供的输入调用该函子,因为这就是在 C++ 中使用 lambda 的含义。然而,在优化的构建中,as-if 规则释放了编译器来内联一些东西,这意味着实际生成的代码可能只是内联 lambda 的内容并完全跳过定义/实例化函子。关于这种内联的讨论已经出现在过去的讨论中,这里和这里,以及其他地方。
题
在我找到的所有 lambda 内联问题和答案中,所提供的示例没有使用任何形式的lambda 捕获,而且它们在很大程度上也与将 lambda 作为参数传递给某物有关(即在std::for_each调用的上下文)。那么,我的问题是:编译器是否仍然可以内联捕获值的 lambda?更具体地说(因为我假设各种变量的生命周期在很大程度上影响了答案),编译器是否可以合理地内联一个仅在定义它的函数内部使用的 lambda,即使它捕获了一些东西(即局部变量)通过引用?
我的直觉是,内联应该是可能的,因为编译器可以完全了解所有代码和相关变量(包括它们相对于 lambda 的生命周期),但我并不乐观,我的汇编阅读技能也不是t 足够多地为我自己得到一个可靠的答案。
附加示例
以防万一我描述的特定用例不是很清楚,这里是上面 lambda 的一个修改版本,它利用了我描述的那种模式(再次,请忽略代码是人为的这一事实并且不必要地过于复杂):
void printSomeNumbers(void)
{
std::ostringstream ss;
const auto …Run Code Online (Sandbox Code Playgroud) 我想知道我的编译器使用以下代码做了什么
void design_grid::design_valid()
{
auto valid_idx = [this]() {
if ((row_num < 0) || (col_num < 0))
{
return false;
}
if ((row_num >= this->num_rows) || (col_num >= this->num_rows))
{
return false;
}
return true;
}
/* some code that calls lambda function valid_idx() */
}
Run Code Online (Sandbox Code Playgroud)
如果我反复调用上面的(design_grid::design_valid)成员函数函数,那么当我的程序遇到valid_idx每次创建时会发生什么?编译器是否在编译时内联后面调用的代码,以便在valid_idx遇到创建时实际上不执行任何操作?
UPDATE
汇编代码的一部分如下.如果这有点太多了,我会稍后发布另一批有色的代码,以说明哪些部分是哪些.(目前没有一种很好的方法可以与我对代码段进行着色).还要注意我已经更新了我的成员函数的定义和上面的lambda函数,以反映它在我的代码中真正命名的内容(因此,在汇编语言中).
在任何情况下,似乎lambda都是与main函数分开定义的.lambda函数由_ZZN11design_grid12design_validEvENKUliiE_clEii下面的函数表示.反过来,在函数的正下方,外部函数(design_grid::design_valid)由_ZN11design_grid12design_validEv开始表示.稍后_ZN11design_grid12design_validEv,打电话给_ZZN11design_grid12design_validEvENKUliiE_clEii.进行调用的这一行看起来像
call _ZZN11design_grid12design_validEvENKUliiE_clEii #
Run Code Online (Sandbox Code Playgroud)
如果我错了,请纠正我,但这意味着编译器将lambda定义为函数外的正常design_valid函数,然后将其作为正常函数调用它应该是什么时候?也就是说,每次遇到声明lambda函数的语句时,它都不会创建新对象?我在该特定位置看到lambda函数的唯一迹线是# tmp85, valid_idx.__this在第二个函数中注释的行,就在函数开始时重新调整的基本和堆栈指针之后,但这只是一个简单的movq操作.
.type _ZZN11design_grid12design_validEvENKUliiE_clEii, …Run Code Online (Sandbox Code Playgroud) 我正在设计一个RGBA类,其中需要将四个参数传递给类的构造函数,以实例化该类.构造函数如下所示:
RGBA(int red = 0, int green = 0, int blue = 0, int alpha = 255)
{
auto valid_color = [](int param) {return (param >= 0 && param <= 255) ? param : 0; };
m_red = valid_color(red);
m_green = valid_color(green);
m_blue = valid_color(blue);
m_alpha = valid_color(alpha);
}
Run Code Online (Sandbox Code Playgroud)
如上所示,我为每个参数使用了lambda来验证传递的参数.这让我想知道,这样一个lambda对函数有什么好处,比如这个(private在接口中声明):
int valid_color(int param)
{
return (param >= 0 && param <= 255) ? param : 0;
}
Run Code Online (Sandbox Code Playgroud)
所以我的选择是这样的:
哪个选项看起来最好,为什么?
c++ ×5
lambda ×5
c++11 ×3
c++17 ×1
class ×1
function ×1
inline ×1
inlining ×1
optimization ×1
performance ×1