Cas*_*eri 5 c++ x86 assembly gcc
首先是一个微不足道的数学事实:给定整数n和m,我们有n < m当且仅当n <= m - 1。
GCC 似乎更喜欢较小绝对值的即时值。因此,当m已知并且满足其他条件时,编译器在等效比较表达式中选择最小化绝对值的表达式。例如,它喜欢n <= 1000在n < 1001和GCC 9.2将这种
bool f(uint32_t n) {
return n < 1001;
}
Run Code Online (Sandbox Code Playgroud)
进入这个x86汇编代码
f(unsigned int):
cmpl $1000, %edi
setbe %al
ret
Run Code Online (Sandbox Code Playgroud)
这可能有很好的性能原因,但这不是我的问题。我想知道的是:有没有办法强制 GCC 保持原始比较?更具体地说,我不担心可移植性,因此,GCC 细节(选项、编译指示、属性等)对我来说是可以的。但是,我正在寻找一个constexpr友好的解决方案,它似乎排除了 inline asm。最后,我的目标是 C++17,它不包括std::is_constant_evaluated. (话虽如此,请尽管不顾我的限制自由地提供答案,因为它可能对其他人仍然有用。)
你可能会问我为什么要做这样的事情。开始了。据我所知(如果我错了,请纠正我)这种行为可能是x86_64以下示例中的“悲观化” :
bool g(uint64_t n) {
n *= 5000000001;
return n < 5000000001;
}
Run Code Online (Sandbox Code Playgroud)
由 GCC 6.2 翻译成
g(unsigned long):
movabsq $5000000001, %rax
imulq %rax, %rdi
movabsq $5000000000, %rax
cmpq %rax, %rdi
setbe %al
ret
Run Code Online (Sandbox Code Playgroud)
在 中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
Run Code Online (Sandbox Code Playgroud)
因此,10movabs字节长的subq指令被 4 字节长的指令取代。无论如何,subq似乎也没有必要。
这是一个 C++20 解决方案,遗憾的是我无法使用
#include <cstdint>
#include <type_traits>
template <class U>
bool less_asm(U n, U m) noexcept {
bool r;
asm("cmp%z[m]\t{%[m], %[n]|%[n], %[m]}"
: "=@ccb"(r) : [n]"r"(n), [m]"re"(m) : "cc");
return r;
}
template <class U>
constexpr bool less(U n, U m) noexcept {
if (std::is_constant_evaluated())
return n < m;
return less_asm(n, m);
}
static_assert(less(uint64_t(0), uint64_t(1)));
bool g(uint64_t n) {
n *= 5000000001;
return less<uint64_t>(n, 5000000001);
}
Run Code Online (Sandbox Code Playgroud)
GCC 9.2(使用 -O2 -std=c++2a)生成:
g(unsigned long):
movabsq $5000000001, %rax
imulq %rax, %rdi
cmpq %rax, %rdi
setc %al
ret
Run Code Online (Sandbox Code Playgroud)
更新:下面的代码片段显示了两项改进:
__builtin_constant_p()代替std::is_constant_evaluated()。之前版本的 GCC 抱怨asm出现在constexpr函数中。)less_asm。)template <class U>
constexpr bool less(U n, U m) noexcept {
if (__builtin_constant_p(n < m))
return n < m;
return [&]{
bool r;
asm("cmp\t{%[m], %[n]|%[n], %[m]}"
: "=@ccb"(r) : [n]"r"(n), [m]"re"(m) : "cc");
return r;
}();
}
Run Code Online (Sandbox Code Playgroud)