C++ 编译器是否对 lambda 闭包执行编译时优化?

Do C++ compilers perform compile-time optimizations on lambda closures?

假设我们有以下(无意义的)代码:

const int a = 0;
int c = 0;
for(int b = 0; b < 10000000; b++)
{
    if(a) c++;
    c += 7;
}

变量 'a' 等于零,因此编译器可以在编译时推断出指令 'if(a) c++;' 永远不会被执行,并将对其进行优化。

我的问题:lambda 闭包也会发生同样的情况吗?

查看另一段代码:

const int a = 0;
function<int()> lambda = [a]()
{
    int c = 0;
    for(int b = 0; b < 10000000; b++)
    {
        if(a) c++;
        c += 7;
    }
    return c;
}

编译器会知道 'a' 是 0 并且会优化 lambda 吗?

更复杂的示例:

function<int()> generate_lambda(const int a)
{
    return [a]()
    {
        int c = 0;
        for(int b = 0; b < 10000000; b++)
        {
            if(a) c++;
            c += 7;
        }
        return c;
    };
}

function<int()> a_is_zero = generate_lambda(0);
function<int()> a_is_one = generate_lambda(1);

当编译器知道 'a' 在生成时为 0 时,它是否足够聪明来优化第一个 lambda?

gcc 或 llvm 有这种优化吗?

我问是因为我想知道当我知道在 lambda 生成时满足某些假设或者编译器会为我做这些时我是否应该手动进行这样的优化。

查看 gcc5.2 -O2 生成的程序集显示使用 std::function:

时优化没有发生
#include <functional>

int main()
{
    const int a = 0;    
    std::function<int()> lambda = [a]()
    {
        int c = 0;
        for(int b = 0; b < 10000000; b++)
        {
            if(a) c++;
            c += 7;
        }
        return c;
    };

    return lambda();
}

编译成一些样板并且

    movl    (%rdi), %ecx
    movl    000000, %edx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    cmpl    , %ecx
    sbbl    $-1, %eax
    addl    , %eax
    subl    , %edx
    jne .L3
    rep; ret

这是您想优化的循环。 (Live) 但如果您实际使用 lambda(而不是 std::function),优化确实会发生:

int main()
{
    const int a = 0;    
    auto lambda = [a]()
    {
        int c = 0;
        for(int b = 0; b < 10000000; b++)
        {
            if(a) c++;
            c += 7;
        }
        return c;
    };

    return lambda();
}

编译为

movl    000000, %eax
ret

即循环被完全删除。 (Live)

Afaik,你可以期望 lambda 的开销为零,但是 std::function 不同并且会带来成本(至少在优化器的当前状态下,尽管人们显然正在研究这个),甚至如果代码 "inside the std::function" 会被优化。 (对此持保留态度,如有疑问请尝试,因为这可能会因编译器和版本而异。std::functions 开销当然可以被优化掉。)

正如@MarcGlisse 正确指出的那样,clang3.6 即使在 std::function 下也执行了所需的优化(相当于上面的第二种情况)。 (Live)

奖金编辑,再次感谢@MarkGlisse:如果包含 std::function 的函数是 而不是 调用 main,则优化发生在 gcc5 中。 2 介于 gcc+main 和 clang 之间,即函数被缩减为 return 70000000; 加上一些额外的代码。 (Live)

奖金编辑 2,这次是我的:如果您使用 -O3,gcc 将 (出于某种原因) 中所述,优化 std::function

cmpl    , (%rdi)
sbbl    %eax, %eax
andl    $-10000000, %eax
addl    000000, %eax
ret

其余的保持在 not_main 的情况下。所以我想在行的底部,使用 std::function.

时只需要测量一下

-O3 的 gcc 和 MSVC2015 版本都不会用这个简单的代码优化它,lambda 实际上会被 调用

#include <functional>
#include <iostream>

int main()
{
    int a = 0;    
    std::function<int()> lambda = [a]()
    {
        int c = 0;
        for(int b = 0; b < 10; b++)
        {
            if(a) c++;
            c += 7;
        }
        return c;
    };

    std::cout << lambda();

    return 0;
}

-O3 这是 gcc 为 lambda 生成的(代码来自 godbolt

lambda:
    cmp DWORD PTR [rdi], 1
    sbb eax, eax
    and eax, -10
    add eax, 80
    ret

这是一种人为的优化方式来表达以下内容:

  • 如果 a 为 0,则第一个比较将设置进位标志 CReax 实际上会设置为 32 个 1 值,and 用 -10 运算(这会在 eax 中产生 -10)然后加上 80 -> 结果是 70.

  • 如果 a 与 0 不同,第一次比较将 设置进位标志 CReax 将被设置为零,and 将没有任何效果,它会被添加 80 -> 结果是 80.

必须注意(感谢 Marc Glisse),如果函数被标记为 cold(即不太可能被调用),gcc 会执行正确的操作并优化调用.

MSVC 生成了更冗长的代码,但不会跳过比较。

Clang 是唯一正确的:lambda 的代码优化程度不及 gcc,但它未被调用

mov edi, std::cout
mov esi, 70
call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)

士气:Clang 似乎做对了,但优化挑战仍然存在。