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

时间:2015-08-30 19:09:30

标签: c++ gcc optimization lambda llvm-clang

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

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

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

我的问题: 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;
}

编译器是否知道&#39; a&#39;是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);

当编译器知道&#39; a&#39;时,编译器是否足够聪明以优化第一个lambda。在世代时是0?

gcc或llvm是否有这种优化?

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

2 个答案:

答案 0 :(得分:12)

查看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    $10000000, %edx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    cmpl    $1, %ecx
    sbbl    $-1, %eax
    addl    $7, %eax
    subl    $1, %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    $70000000, %eax
ret

即。完全删除了循环。 (Live

Afaik,你可以期望lambda的开销为零,但是std::function是不同的并且带有成本(至少在优化器的当前状态下,尽管人们显然在这方面工作),即使代码“std::function”内部已经过优化。 (带上一粒盐然后尝试,如果有疑问,因为这可能会在编译器和版本之间有所不同。std::function的开销当然可以被优化掉。)

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

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

奖励编辑2,这次是我的:如果您使用-O3,gcc将,(由于某种原因),如Marco's answer中所述,优化std::function到< / p>

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

并将其余部分保留在not_main案例中。所以我想在线的底部,只需要在使用std::function时进行测量。

答案 1 :(得分:3)

-O3和MSVC2015 Release的gcc都不会使用这个简单的代码来优化它,而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 - &gt;结果是70。

  • 如果a与0不同,则第一次比较设置进位标记CReax将设置为零,and没有任何效果,它将被添加80 - &gt;结果是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似乎做得对,但优化挑战仍然是开放的。