自执行 C++11 lambda 的成本
Cost of self-executing C++11 lambdas
在 window procedure 之外,我正在使用自执行的 lambda 编写 switch
语句,如下所示:
LRESULT CALLBACK proc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_CREATE: return [&](WPARAM wp, LPARAM lp) {
do_something(wp, lp);
return 0;
}(wp, lp);
case WM_SIZE: return [&](HWND hWnd) {
do_another_thing(hWnd);
return 0;
}(hWnd);
}
return DefWindowProc(hWnd, msg, wp, lp);
}
我相信编译器可以按照他们想要的方式自由优化它,但一般来说,与不使用这些 lambda 相比,编译器会为此添加很多样板代码吗?
编译器能否检测到冗余的 lambda 并将其删除?
问题有点奇怪。编译器不会 "remove" lambda,因为 lambda 在您的 源代码 中,并且编译器不会修改您的源代码。它所做的是发出机器代码,产生您在源代码中表达的程序的行为。
编译器可以随意发出尽可能多或尽可能少的机器代码,只要结果按照您的程序表达的方式运行即可。
如果编译器可以将所有代码内联到一个地方,那么它当然不必在它们之间发出单独的函数体和jumps/calls,这是一个常见的应用优化。
像这样的优化问题没有明确的答案,因为兼容编译器的优化器可以做很多很多事情。然而,在这种情况下,大多数现代优化器几乎肯定会内联 lambda,并生成相同的程序集,无论您是否使用 lambda。因为 lambda 有一个独特的类型,编译器 可以 轻松内联。因为 lambda 被声明并立即使用并且从未被赋值(一个更常见的名称是 "immediately invoked/evaluated lambda instead of "self executing"),编译器知道它只能被调用一次。所以通常它会决定内联。
当然,您可以查看一些程序集:https://godbolt.org/g/QF6WmR。如您所见,此特定示例中生成的程序集是相同的,但显然不能证明一般情况。
一般来说,lambda 被认为是 C++ 中的低成本或零成本抽象,如果您认为 lambda 可以生成最干净的代码,那么请使用它。如果需要,您可以随时快速验证装配是否相同。不过,您以这种方式使用 lambda 的原因有点不寻常;我不会真的认为代码折叠是一个很好的理由。使用立即计算的 lambdas 的一个更常见的原因是能够在其他情况下不能使用 const
:
int x;
try {
x = foo();
}
catch (const ExceptionType& e) {
x = bar();
}
对
const auto x = [] () {
try {
return foo();
}
catch (const ExceptionType& e) {
return bar();
}
}();
在传统的C++代码中,要在外部范围内持久化x
,我们必须先声明它,然后再赋值给它。通过使用 returns 我们想要的值的 lambda,我们可以同时声明和分配 x
,允许它是 const
.
在 window procedure 之外,我正在使用自执行的 lambda 编写 switch
语句,如下所示:
LRESULT CALLBACK proc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_CREATE: return [&](WPARAM wp, LPARAM lp) { do_something(wp, lp); return 0; }(wp, lp); case WM_SIZE: return [&](HWND hWnd) { do_another_thing(hWnd); return 0; }(hWnd); } return DefWindowProc(hWnd, msg, wp, lp); }
我相信编译器可以按照他们想要的方式自由优化它,但一般来说,与不使用这些 lambda 相比,编译器会为此添加很多样板代码吗?
编译器能否检测到冗余的 lambda 并将其删除?
问题有点奇怪。编译器不会 "remove" lambda,因为 lambda 在您的 源代码 中,并且编译器不会修改您的源代码。它所做的是发出机器代码,产生您在源代码中表达的程序的行为。
编译器可以随意发出尽可能多或尽可能少的机器代码,只要结果按照您的程序表达的方式运行即可。
如果编译器可以将所有代码内联到一个地方,那么它当然不必在它们之间发出单独的函数体和jumps/calls,这是一个常见的应用优化。
像这样的优化问题没有明确的答案,因为兼容编译器的优化器可以做很多很多事情。然而,在这种情况下,大多数现代优化器几乎肯定会内联 lambda,并生成相同的程序集,无论您是否使用 lambda。因为 lambda 有一个独特的类型,编译器 可以 轻松内联。因为 lambda 被声明并立即使用并且从未被赋值(一个更常见的名称是 "immediately invoked/evaluated lambda instead of "self executing"),编译器知道它只能被调用一次。所以通常它会决定内联。
当然,您可以查看一些程序集:https://godbolt.org/g/QF6WmR。如您所见,此特定示例中生成的程序集是相同的,但显然不能证明一般情况。
一般来说,lambda 被认为是 C++ 中的低成本或零成本抽象,如果您认为 lambda 可以生成最干净的代码,那么请使用它。如果需要,您可以随时快速验证装配是否相同。不过,您以这种方式使用 lambda 的原因有点不寻常;我不会真的认为代码折叠是一个很好的理由。使用立即计算的 lambdas 的一个更常见的原因是能够在其他情况下不能使用 const
:
int x;
try {
x = foo();
}
catch (const ExceptionType& e) {
x = bar();
}
对
const auto x = [] () {
try {
return foo();
}
catch (const ExceptionType& e) {
return bar();
}
}();
在传统的C++代码中,要在外部范围内持久化x
,我们必须先声明它,然后再赋值给它。通过使用 returns 我们想要的值的 lambda,我们可以同时声明和分配 x
,允许它是 const
.