考虑以下局部变量的声明:
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
的程度。 .但相反,我使用所有编译器(无论优化级别如何)得到的结果是某种形式的: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 中自己尝试上面的代码.
不同编译器的行为如此一致,以至于我不得不认为上述低效率是有原因的,但我一直找不到。你知道为什么吗?
最佳答案
编译器是愚蠢的,这是一个遗漏的优化。 mov qword ptr [rsp], 0
将是最佳的。从 qword 存储到任何单个字节的字节重新加载的存储转发在现代 CPU 上是有效的。 ( https://blog.stuffedcow.net/2014/01/x86-memory-disambiguation/ )
(或者甚至更好, push 0
而不是 sub rsp, 8
+ mov
, also a missed optimization 因为编译器不会费心寻找可能的情况。)
大概是在确定堆栈帧中局部变量相对于彼此的位置之前,寻找存储合并的优化 channel 会运行。 (或者甚至在决定哪些本地变量可以保存在寄存器中以及哪些根本需要内存地址之前。)
存储合并又名合并直到最近才在 GCC8 IIRC 中重新引入,之后作为从 GCC2.95 到 GCC3 的回归被丢弃,同样是 IIRC。 (我认为其他优化,例如假设没有严格的别名违规以在更多时间将更多变量保留在寄存器中,更有用)。所以它已经消失了几十年。
从一个 POV 来看,您可以说自己很幸运,您完全可以合并任何存储(具有结构成员和数组元素,这些元素在早期就知道是相邻的)。当然,从另一个 POV 来看,理想情况下,编译器应该制作好的 asm。但在实践中,错过的优化很常见。幸运的是,我们有强大的 CPU,具有广泛的超标量乱序执行,通常可以通过这些垃圾来快速查看即将到来的缓存未命中加载和存储,因此浪费的指令有时有时间在其他瓶颈的阴影下执行。这并不总是正确的,并且在乱序执行窗口中堵塞空间从来都不是一件好事。
相关: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
之外的常量的一般情况, re: 最佳的 asm 是什么。 (数组与单独的局部变量之间的区别仅在那里的评论中讨论。)
关于c++ - 为什么 x86-64 C/C++ 编译器没有为此代码生成更高效的汇编?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63355199/