【问题标题】:Using bts assembly instruction with gcc compiler使用 bts 汇编指令和 gcc 编译器
【发布时间】:2010-12-31 07:07:33
【问题描述】:

我想在 Mac 上使用 bts 和 bt x86 汇编指令来加快我的 C++ 代码中的位操作。在 Windows 上,_bittestandset 和 _bittest 内部函数运行良好,并提供显着的性能提升。在 Mac 上,gcc 编译器似乎不支持这些,所以我尝试直接在汇编程序中进行。

这是我的 C++ 代码(注意“位”可以 >= 32):

typedef unsigned long LongWord;
#define DivLongWord(w) ((unsigned)w >> 5)
#define ModLongWord(w) ((unsigned)w & (32-1))

inline void SetBit(LongWord array[], const int bit)
{
   array[DivLongWord(bit)] |= 1 << ModLongWord(bit);
}

inline bool TestBit(const LongWord array[], const int bit)
{
    return (array[DivLongWord(bit)] & (1 << ModLongWord(bit))) != 0;
}

以下汇编代码有效,但不是最佳的,因为编译器无法优化寄存器分配:

inline void SetBit(LongWord* array, const int bit)
{
   __asm {
      mov   eax, bit
      mov   ecx, array
      bts   [ecx], eax
   }
}

问题:如何让编译器围绕 bts 指令进行全面优化?以及如何用 bt 指令替换 TestBit?

【问题讨论】:

  • 不是直接的答案,只是指向 cims.nyu.edu/cgi-systems/… Extended Asm 文档的链接。
  • 由于疯狂的 CISC 语义,带有内存操作数的 BT* 很慢。让编译器发出您使用此代码获得的移位/或(或 TEST)指令序列实际上更快。 (至少,这是一个修正错误的 64 位干净版本)。

标签: c++ performance macos gcc assembly


【解决方案1】:

BTS(和其他BT* insns)具有内存目标are slow. (>10 uops on Intel)。通过进行地址数学运算以找到正确的字节并将其加载到寄存器中,您可能会获得更快的代码。然后您可以使用注册目标执行BT / BTS 并存储结果。

或者可以将1 移动到正确的位置,并将OR 与SetBit 的内存目标一起使用,或者将ANDTestBit 的内存源一起使用。当然,如果你避免内联汇编,编译器可以内联 TestBit 并使用 TEST 而不是 AND,这在某些 CPU 上很有用(因为它可以宏融合到更多 CPU 上的测试和分支比AND)。

This is in fact what gcc 5.2 generates from your C source(内存目标ORTEST)。对我来说看起来是最佳的(比内存目标bt 更少的微指令)。实际上,请注意您的代码已损坏,因为它假定 unsigned long 是 32 位,而不是 CHAR_BIT * sizeof(unsigned_long)。使用uint32_tchar 会是一个更好的计划。注意 eax 的符号扩展为 raxcqde 指令,由于写得很糟糕的 C 使用 1 而不是 1UL

另请注意,内联 asm 无法返回标志作为结果(new-in-gcc v6 extension! 除外),因此对 TestBit 使用内联 asm 可能会导致糟糕的代码代码,例如:

...  ; inline asm
bt   reg, reg
setc al       ; end of inline asm
test al, al   ; compiler-generated
jz bit_was_zero

现代编译器可以并且确实在适当的时候使用BT(带有寄存器目标)。最终结果:您的 C 可能编译成比您建议使用内联汇编更快的代码。在被修正为正确和 64 位干净之后,它会更快。如果您正在优化代码大小,并且愿意付出显着的速度损失,那么强制使用 bts 可能会起作用,但 bt 可能仍然无法正常工作(因为结果进入了标志)。

【讨论】:

  • 实际上,从 v6 开始,gcc can 返回标志。
  • @DavidWohlferd:谢谢!有趣的是,他们最终将其包括在内。
  • @PeterCordes 你见过编译器生成bts吗?我确实至少可以得到clangicc 为“测试位”类型functions 生成bt,但对bts 没有任何运气,尽管它似乎对“设置位”非常有用类型函数与使用 shlshlxor 的替代方法以及几个额外的操作来加载常量 1 等相比。
  • @BeeOnRope:我忘了。您所描述的听起来很熟悉:使用bt 测试位,但未能使用bts 设置位。我很确定这至少在英特尔 CPU 上是一场胜利,对于像 foo |= 1&lt;&lt;n 这样的东西,n 不是编译时常量,而 foo 已经在寄存器中。是的,刚刚测试过,没有运气:godbolt.org/g/9s6i9D
  • 是的,这正是我在 Godbolt 链接中测试的 idom。我确实找到了一种仅在icc 上生成bts 的方法,在compile-time bit index 的特定情况下。 gccclang 都没有在那里使用 bts,如果位置是可变的,icc 不会使用它(可以说它更有用,因为用于可变移位的非bts 代码通常是甚至比常量情况还要慢,尤其是 pre-SHLX)。
【解决方案2】:
inline void SetBit(*array, bit) {
    asm("bts %1,%0" : "+m" (*array) : "r" (bit));
}

【讨论】:

  • 完美,谢谢。这帮助我弄清楚了我的第二个问题: inline bool TestBit(const LongWord array[], const int bit) { bool flag; asm("bt %2,%1; setb %0" : "=q" (flag) : "m" (*array), "r" (bit));返回标志; }
  • @ephemient:您能否为您的答案添加解释。
  • 如果bit 可能在 0..31 范围之外,您的内存操作数应该是整个数组,而不仅仅是它的第一个元素。 (请记住带有内存操作数的 bts 的疯狂 CISC 位串语义。)此外,您应该让源操作数为 "ri",因为它适用于立即操作数(并且这样慢得多)。但实际上你根本不应该在现代 x86 上使用它。 bts 带有内存操作数是 slow (见我的回答)。
  • 这条指令破坏了 C 标志,但程序集没有指定。
  • @MaximEgorushkin:在 x86 和 x86-64 的 GNU C 内联汇编中隐含了一个“cc”clobber。这不是错误,尽管有些人认为手动指定它是一种很好的风格。
【解决方案3】:

另一个稍微间接的答案,GCC 从 4.1 版本开始公开a number of atomic operations

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-03
    相关资源
    最近更新 更多