为什么 x86-64 C/C++ 编译器没有为这段代码生成更高效的汇编?

Why are x86-64 C/C++ compilers not generating more efficient assembly for this code?

考虑以下局部变量声明:

bool a{false};
bool b{false};
bool c{false};
bool d{false};
bool e{false};
bool f{false};
bool g{false};
bool h{false};

在 x86-64 架构中,我希望优化器将这些变量的初始化减少到 mov qword ptr [rsp], 0 之类的东西。但是我可以尝试使用 all 编译器(无论优化级别如何)得到的是某种形式的:

mov     byte ptr [rsp + 7], 0
mov     byte ptr [rsp + 6], 0
mov     byte ptr [rsp + 5], 0
mov     byte ptr [rsp + 4], 0
mov     byte ptr [rsp + 3], 0
mov     byte ptr [rsp + 2], 0
mov     byte ptr [rsp + 1], 0
mov     byte ptr [rsp], 0

这似乎是在浪费 CPU 个周期。使用复制初始化、值初始化或用圆括号替换大括号都没有区别。

但是等等,这还不是全部。假设我有这个:

struct
{
    bool a{false};
    bool b{false};
    bool c{false};
    bool d{false};
    bool e{false};
    bool f{false};
    bool g{false};
    bool h{false};
} bools;

然后 bools 的初始化生成的正是我所期望的:mov qword ptr [rsp], 0。给出了什么?

您可以在 this Compiler Explorer link.

中自己尝试上面的代码

不同编译器的行为是如此一致,以至于我不得不认为上述低效率是有某种原因的,但我一直没能找到它。你知道为什么吗?

编译器很笨,这是missed-optimization。 mov qword ptr [rsp], 0 将是最佳选择。从 qword 存储到任何单个字节的字节重新加载的存储转发在现代 CPU 上是高效的。 (https://blog.stuffedcow.net/2014/01/x86-memory-disambiguation/)

(或者甚至更好,push 0 而不是 sub rsp, 8 + mov 因为编译器不会费心寻找可能的情况。)


大概是在确定局部变量在堆栈帧中相对于彼此的位置之前寻找存储合并运行的优化过程。 (或者甚至在决定哪些局部变量可以保存在寄存器中以及哪些根本不需要内存地址之前。)

存储合并又名合并直到最近才在 GCC8 IIRC 中重新引入,在作为从 GCC2.95 到 GCC3 的回归中被删除,再次是 IIRC。 (我认为其他优化,例如假设没有 strict-aliasing 违规以在更多时间将更多变量保存在寄存器中,更有用)。所以几十年都不见了

从一个 POV 来看,您可以说自己很幸运,您正在合并所有商店(具有结构成员和数组元素,这些元素很早就知道是相邻的)。当然,从另一个 POV 来看,理想情况下,编译器应该制作出好的 asm。但在实践中,错过优化很常见。幸运的是,我们拥有强大的 CPU,具有广泛的超标量 out-of-order 执行 通常 咀嚼这些废话仍然可以很快看到即将到来的缓存未加载和存储,因此浪费的指令有时有时间执行在其他瓶颈的阴影下。这并不总是正确的,在 out-of-order 执行 window 中阻塞 space 从来都不是 的事情。

相关:In x86-64 asm: is there a way of optimising two adjacent 32-bit stores / writes to memory if the source operands are two immediate values? 涵盖了除 0 以外的常量的一般情况,回复:最佳 asm 是什么。 (数组与单独局部变量之间的区别仅在评论中讨论过。)