首先是一个微不足道的数学事实:给定整数n
和m
,我们有n < m
当且仅当 ,n <= m - 1
。
GCC 似乎更喜欢绝对值较小的直接值。因此,当m
已知且满足其他条件时,编译器会在等效比较表达式中选择一个最小化绝对值的表达式。例如,它n <= 1000
比n < 1001
GCC 9.2 更喜欢这个
bool f(uint32_t n) {
return n < 1001;
}
进入这个x86
汇编代码
f(unsigned int):
cmpl $1000, %edi
setbe %al
ret
这可能有良好的性能原因,但这不是我的问题。我想知道的是:有没有办法强制 GCC 保持原来的比较?更具体地说,我不担心可移植性,因此 GCC 细节(选项、编译指示、属性……)对我来说是可以的。但是,我正在寻找一种constexpr
似乎排除 inline 的友好解决方案asm
。最后,我的目标是 C++17,它不包括std::is_constant_evaluated
. (话虽如此,请随意提供答案,不管我的限制如何,因为它可能对其他人仍然有用。)
你可能会问我为什么要做这样的事情。开始了。据我了解(如果我错了,请纠正我)这种行为可能是x86_64
以下示例中的“悲观”:
bool g(uint64_t n) {
n *= 5000000001;
return n < 5000000001;
}
由 GCC 6.2 翻译成
g(unsigned long):
movabsq $5000000001, %rax
imulq %rax, %rdi
movabsq $5000000000, %rax
cmpq %rax, %rdi
setbe %al
ret
在x86_64
中,使用 64 位立即数的计算有一些限制,可能意味着这些值要加载到寄存器中。在上面的例子中,这发生了两次:常量5000000001
和5000000000
存储在rax
乘法和比较中。如果 GCC 保留 C++ 代码中出现的原始比较(即反对5000000001
),则不需要第二个movabs
.
这也意味着代码大小的损失,我猜这被认为是一个问题,并且最新版本的 GCC(例如 9.2)产生了这个:
g(unsigned long):
movabsq $5000000001, %rax
imulq %rax, %rdi
subq $1, %rax
cmpq %rax, %rdi
setbe %al
ret
因此,10movabs
字节长的指令被 4 字节长的subq
指令取代。无论如何,subq
也似乎没有必要。