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

C++ 在比较中,GCC似乎更喜欢小的立即值。有没有办法避免这种情况?,c++,gcc,assembly,x86,C++,Gcc,Assembly,X86,首先是一个微不足道的数学事实:给定整数n和m,我们有n来询问表达式是否具有编译时常量值(在常数传播优化之后),不管C++ CONEXPRO.@ Per-Errordes,我以前都尝试过,但它没有用。它甚至让我对std::is_constant\u evaluated和\u builtin\u constant\u p的理解感到困惑。现在它可以工作了(从GCC 9.1开始)。试着把这个写成答案。你忘了在前一次启用优化吗?如果不进行优化,\uuuuuu内置常数p(var)将始终为false(除了常量

首先是一个微不足道的数学事实:给定整数
n
m
,我们有
n
当且仅当,
n这里有一个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);
}
更新:下面的代码片段显示了两个改进:

  • 它适用于C++17,但需要GCC-9.1或更高版本。(由于“建议使用
    \uu内置常量\u p()
    而不是
    std::is\u constant\u evaluated()
    。以前版本的GCC抱怨
    asm
    出现在
    constepr
    函数中。)
  • 它在作用域中引入的名称更少。(使用lambda而不是
    less\u asm

  • 模板
    constexpr bool less(U n,U m)无例外{
    if(uuu内置常数_up(n
    gcc 9.2.1在Linux上使用-O2提供:

    movabsq $5000000001, %rax
    imulq   %rax, %rdi
    subq    $1, %rax
    cmpq    %rax, %rdi
    setbe   %al
    ret
    

    因此,除非您真的担心减法,否则您似乎不需要做任何特殊的事情。

    通常,强制GCC输出您可能需要的特定指令序列的唯一方法是使用汇编。如果GCC不能生成最佳的代码,最好的办法就是提交一个bug报告,以便在将来的版本中得到改进

    对于您的特定情况,至少有一种变通方法可以生成您想要的代码,至少对于当前的编译器是这样。虽然它不需要在汇编中写入整个代码序列,但它确实使用内联汇编将常量加载到寄存器中,前提是GCC更喜欢使用该寄存器,而不是生成新的常量值进行比较

    #include <stdint.h>
    
    static uint64_t 
    load_const_asm(uint64_t c) {
        if (__builtin_constant_p(c) && (int32_t) c != c) {
               asm("" : "+r" (c));
        }
        return c;
    }
    
    bool
    g(uint64_t n) {
        uint64_t c = load_const_asm(5000000001);
        n *= c;
        return n < c;
    }
    
    这个asm语句欺骗编译器不生成一个单独的常量负1的加载,因为编译器不知道asm语句做什么,即使它只是一个空字符串。它将常量强制放入寄存器,但编译器不知道asm语句“执行”后寄存器将包含什么

    与使用带CMP指令的asm语句相比,这种方法的优点是,它使编译器能够最大限度地自由地优化代码。这是内联汇编的一个很大的缺点,一般来说,它很容易让您的代码失望。例如,如果
    n
    是常数,则使用内联汇编可以防止编译器在编译时计算g()的结果。然而,通过上面的代码示例,它仍然能够计算出,如果
    n
    为1,则g()必须返回true


    最后,确保您没有试图过早地优化代码。如果这样做不会对应用程序的性能造成任何明显的影响,那么您不希望像这样混淆代码。如果您最终使用了此代码或其他黑客,那么正如Peter Cordes在评论中所说,您应该清楚地评论您的代码,解释为什么需要此黑客,以便在不再需要时将其删除。

    我确实在我的原始帖子中显示了上面的代码,正如我所说的,减法是可以避免的。这仍然是一个额外的uop,只保存部分代码大小。(尽管
    movabs
    非常糟糕,并且从uop缓存中获取的速度可能很慢。)报告上的gcc missed优化错误。用源代码把持编译器在短期内可能是有用的,但真正的修复是GCC考虑CSE的两种选项,当它不适合于即时(或总是;RG操作数如果在另一个原因已经在Reg中时占用更少的空间)。此优化可帮助gcc尽可能使用
    imm8
    而不是
    imm32
    ,或使用符号\u extended\u imm32而不是movab。但是当它不在尖端时,它就没有用处了。@Peter我完全同意,这就是我暂时想使用源代码技巧的原因。在missed optimization报告中,我想包括一些测量结果,表明使用源代码技巧生成的代码确实比不使用源代码技巧生成的代码要快。我希望说服编译器编写人员将“技巧”转化为真正的解决方案。目前,我正在使用内联asm来证明我的观点。(事实上,我手头缺少的优化问题范围更广,这个比较只是其中的一部分。)如果你想进行微基准测试,手工编辑的asm是一种更容易证明你观点的方法。GCC开发人员会认为这是一个明显的问题,即使没有这个问题也值得解决。但是可以肯定的是,一个真实的基准测试显示了显著的减速,这可能会促使人们更快地在上面花费时间。fwiw,gcc 7.1,然后用次立即数1替换乘法示例中的第二个mov。CLAN重用您希望的值,而不是<代码> STD::ISHOntTangTyAsdioType()/Cord>可以使用GNU C++代码> SuxBuffTynOntActTangSp(N< M)< /Cord>来询问表达式是否具有编译时常量值(在常数传播优化之后),不管C++ CONEXPRO.@ Per-Errordes,我以前都尝试过,但它没有用。它甚至让我对
    std::is_constant\u evaluated
    \u builtin\u constant\u p
    的理解感到困惑。现在它可以工作了(从GCC 9.1开始)。试着把这个写成答案。你忘了在前一次启用优化吗?如果不进行优化,
    \uuuuuu内置常数p(var)
    将始终为false(除了
    常量
    ),因为GCC不会在
    -O0
    处跨语句进行优化。我真的不知道std::is_constant_evaluated()的作用是什么。我不认为这真的需要一个单独的答案;它只是对你的一个小的调整,已经使用了内联的ASM,如果你只想在一个基准中演示,我认为答案的要点。彼得:不,我没有忘记。以前的问题是编译器支持。它从8.3版更改为9.1版,如图所示:(我已经
    g(unsigned long):
      movabsq $5000000001, %rax
      imulq %rax, %rdi
      movabsq $5000000000, %rax
      cmpq %rax, %rdi
      setbe %al
      ret
    
    g(unsigned long):
      movabsq $5000000001, %rax
      imulq %rax, %rdi
      subq $1, %rax
      cmpq %rax, %rdi
      setbe %al
      ret
    
    #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);
    }
    
    g(unsigned long):
      movabsq $5000000001, %rax
      imulq %rax, %rdi
      cmpq %rax, %rdi
      setc %al
      ret
    
    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;
      }();
    }
    
    movabsq $5000000001, %rax
    imulq   %rax, %rdi
    subq    $1, %rax
    cmpq    %rax, %rdi
    setbe   %al
    ret
    
    #include <stdint.h>
    
    static uint64_t 
    load_const_asm(uint64_t c) {
        if (__builtin_constant_p(c) && (int32_t) c != c) {
               asm("" : "+r" (c));
        }
        return c;
    }
    
    bool
    g(uint64_t n) {
        uint64_t c = load_const_asm(5000000001);
        n *= c;
        return n < c;
    }
    
    _Z1gm:
      movabs rax, 5000000001
      imul rdi, rax
      cmp rdi, rax
      setb al
      ret