繁体   English   中英

除非返回值具有名称,否则为什么GCC无法优化?

[英]Why does GCC fail to optimize unless the return value has a name?

考虑以下代码:

#include <array>

class C
{
    std::array<char, 7> a{};
    int b{};
};

C slow()
{
    return {};
}

C fast()
{
    C c;
    return c;
}

GCC 6到9产生了很slow()代码为slow()

slow():
        xor     eax, eax
        mov     DWORD PTR [rsp-25], 0
        mov     BYTE PTR [rsp-21], 0
        mov     edx, DWORD PTR [rsp-24]
        mov     DWORD PTR [rsp-32], 0
        mov     WORD PTR [rsp-28], ax
        mov     BYTE PTR [rsp-26], 0
        mov     rax, QWORD PTR [rsp-32]
        ret
fast():
        xor     eax, eax
        xor     edx, edx
        ret

两种功能在含义上有区别吗? Clang都发出类似fast()代码,而GCC 4-5比6-9做得更好,但也不是最优。

构建标志: -std=c++11 -O3

演示: https//godbolt.org/z/rPNG9o


根据此处的反馈作为GCC错误提交: https : //gcc.gnu.org/bugzilla/show_bug.cgi?id=90883

GCC维护人员同意这是一个错误(缺少优化),并且已在x86_64的主干中修复(ARM可能会在以后修复): https : //gcc.gnu.org/bugzilla/show_bug.cgi?id=90883

这并不是真正的完整答案,但可能会提供一个线索。 我怀疑fastslow含义之间存在细微差别,这可能会使编译器沿不同的路径前进。 如果将复制构造函数设为私有,则可以看到此信息。

https://godbolt.org/z/FMIRe3

#include <array>

class C
{
    std::array<char, 7> a{};

    public:
    C(){}

    private:
    C(const C & c){}
};

// Compiles
C slow()
{
    return {};
}

// Does not compile
C fast()
{
    C c;
    return c;
}

即使使用复制省略, fast仍然需要复制构造函数在其中,它以slow返回initialization list ,该initialization list由调用者显式构造返回值。 这些最终可能会或可能不会做同样的事情,但是我相信编译器必须进行一些操作才能确定是否是这种情况。

有一篇详细的博客文章提供了有关此主题的一些有趣背景

https://akrzemi1.wordpress.com/2018/05/16/rvalues-redefined/

但是,该行为在C ++ 17中已更改

鉴于

#include <array>

class C
{
    std::array<char, 7> a{};

    public:
    C(){}

    private:
    C(const C & c){}
};

C slow()
{
    return {};
}

C fast()
{
    return C();
}

fast将无法在C ++ 11下编译,而现在可以在C ++ 17下编译

https://godbolt.org/z/JG2PkD

原因是, return C()的含义从返回临时值变为在调用者框架中显式构造对象。

所以现在在C ++ 17中,

C fast(){
    C c;
    return c;
}

C fast(){
    return C();
}

因为在第二个版本中,您甚至不需要复制或移动构造函数就可以使用。

https://godbolt.org/z/i2eZnf

绝对不是C ++ 101

这两个函数是等效的 :通过使用默认成员初始化程序初始化每个成员,可以初始化返回的对象(更准确地说,是对这些函数的假设调用的结果对象)。

对于slow

=>因此,调用slow的结果对象的所有成员都将使用其默认成员初始化程序dcl.init.aggr] /5.4进行初始化。

为了fast

=>因此,调用slow的结果对象的所有成员都将使用其默认成员初始化程序[class.base.init] /9.1进行初始化

两个函数结果程序集在功能上是等效的 因此,Gcc生产的组件符合标准。

在缓慢的情况下,组装只是次优的。 对象相应地返回到两个寄存器上的SystemV x86 abi:rax和rdx(edx)。 首先,它将栈中位于地址[rsp-32]上类C的概念上的对象清零。 它归零a之间的填充字节abb 然后,它将已初始化的堆栈部分复制到寄存器中。 将堆栈清零的方式只是次优,所有这些操作都等同于fast组装的2 xor操作。 因此,这只是一个明显的错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM