在下面的C++示例中,我定义了一个计算树最左边路径高度的函数。
struct TreeNode {
int value{};
TreeNode *l = nullptr;
TreeNode *r = nullptr;
};
int getLeftHeight(TreeNode *root) {
int height = 0;
while (root != nullptr) {
root = root->l;
height++;
}
return height;
}
在用GCC 9.3或10.1编译时(使用-o3),我得到以下x86。
getLeftHeight(TreeNode*):
xor eax, eax
test rdi, rdi
je .L4
.L3:
mov rdi, QWORD PTR [rdi+8]
add eax, 1
test rdi, rdi
jne .L3
ret
.L4:
ret
但是,当使用Clang 10进行编译时,如下所示,没有一个重复的ret
。
getLeftHeight(TreeNode*):
xor eax, eax
test rdi, rdi
je .LBB0_3
.LBB0_1:
mov rdi, qword ptr [rdi + 8]
add eax, 1
test rdi, rdi
jne .LBB0_1
.LBB0_3:
ret
海合会为什么要这样做?
据我所知,ret
将需要额外的一个字节来进行编码。 在这种情况下,由于发出的机器代码的对齐/填充,这可能无关紧要,但如果情况并非如此,并且这种重复发生在大型代码库中的所有地方,那么许多字节的机器代码最终可能只是重复的ret
s。
这种重复的特殊情况似乎也很容易发现和优化掉。
这似乎是GCC中错过的一个优化。
有一个bug#71923,其中有一个非常类似的例子,标记为“Missing-Optimization”。
话虽如此,但对性能的影响是微不足道的--主要是对代码大小的影响。 此外,当第二个ret
被对齐时(现代x86目标通常就是这种情况),消除第一个ret
通常不会对代码大小产生影响,但是如果消除,从循环中掉下来将导致在ret
之前解码额外的nop
指令,这会使代码路径变得更慢。
无论如何,总是欢迎向GCC发出拉请求。 您可以使用-fdump-passs
查看优化传递列表,并使用-fdump-tree-...
标志转储RTL/Gimple树(或者在Godbolt上查看)。 主要的问题是,现有的优化程序是否应该处理这个问题,或者是否需要添加一个新的优化程序(我不是GCC方面的专家,所以不能建议这样或那样的方法)。