在比较中,GCC 似乎更喜欢小的即时值。有没有办法避免这种情况?

Cas*_*eri 5 c++ x86 assembly gcc

首先是一个微不足道的数学事实:给定整数nm,我们有n < m当且仅当n <= m - 1

GCC 似乎更喜欢较小绝对值的即时值。因此,当m已知并且满足其他条件时,编译器在等效比较表达式中选择最小化绝对值的表达式。例如,它喜欢n <= 1000n < 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 位立即数的计算有一些限制,可能意味着将这些值加载到寄存器中。在上面的例子中,这发生了两次:常量50000000015000000000存储在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似乎也没有必要。

Cas*_*eri 2

这是一个 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)

更新:下面的代码片段显示了两项改进:

  1. 它适用于 C++17,但需要 GCC-9.1 或更高版本。(感谢Peter Cordes建议使用__builtin_constant_p()代替std::is_constant_evaluated()。之前版本的 GCC 抱怨asm出现在constexpr函数中。)
  2. 它在范围内引入了更少的名称。(通过使用 lambda 而不是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)