组装中的位测试后的条件XOR

问题描述 投票:1回答:1

[我正在尝试做一些内联汇编,以在输入b的给定位置测试一点,如果它的a为1,它将用XOR b替换b。

我有错误“ bt的操作数大小不匹配”。

当我使用-O3进行编译时,有时看起来像预期的那样工作。它完全不一致。有时包括正确地进行计算,有时在编译时给出错误。 (全部带有-O3)。

没有-O3,总是在编译时给出错误。

对此的总体要求是尽可能快,并且可以在大多数现代AMD64处理器上运行。

unsigned long ConditionAdd(const unsigned long a, unsigned long b, const long pos) {

  // Intended behavior: Looks at bit in position pos in b: if that bit is a 1, then it replaces b with a xor b
  asm (
       "BT %[position], %[changeable] \n\t"
       "JNC to_here%=\n\t"
       "XOR %[unchangeable], %[changeable]\n\t"
       "to_here%=: "
       : [changeable] "=&r" (b)
       : [position] "mr" (pos), [unchangeable] "mr" (a), "[changeable]" (b)
       : "cc"
       );

  return b;
}
gcc assembly g++ x86-64 inline-assembly
1个回答
0
投票

没有-O3,总是在编译时给出错误。

您为编译器选择了bt源操作数的内存。在禁用优化的情况下,它会这样做,因此结果不会合并。 btbt $imm, r/m是唯一可编码的形式。 (在Intel语法中,如手册bt %reg, r/mbt r/m, imm。)>

[幸运的是,您没有给bt r/m, reg选择内存目标的选项,因为bt具有疯狂的CISC位字符串语义,这使得它在reg,mem情况下非常慢,例如Ryzen上为5 uops,10 uops在SnB系列上。 (bt和/或https://agner.org/optimize/)。您始终希望编译器首先将操作数加载到寄存器中。

您最多只读取一次https://uops.info,因此将其存储在内存中是合理的。 OTOH,如果您选择a"m",则clang始终会选择"rm"。这是一个已知的错过优化错误,但是如果您关心clang,通常最好将编译器赋予该选项not

,否则它将在asm语句之前溢出寄存器。

[不要忘记允许"mr"a使用立即数。

pos采用32位符号扩展的立即数,因此需要xor。 64位移位计数可以使用"e" constraint约束限制为0..63中的立即数,也可以让"e"为您屏蔽(即模)源操作数,即使它是立即数也是如此。但是,GAS使用不适合"J"bt立即数是一个汇编时错误。因此,您可以通过仅使用bt约束来使用它来检测太大的编译时常数imm8。您也可以想象当pos为数字时,执行i asm宏操作来执行.ifeq,否则只需执行%[pos] & 63

BTW,您也可以使用更简单的%[pos]读/写约束,而不是输出+匹配约束。这是一种告诉编译器完全相同的东西的紧凑方式。

而且,您不需要早熟人

%[pos]不会修改任何整数寄存器,只会修改EFLAGS,因此不会在仅读取输入操作数的最终读取之前中写入任何寄存器。如果已知[changeable] "+&r"(b)bt保持相同的值,则将生成的asm具有a(归零成语)作为掉线路径是完全可以的。
b

对此的总体要求是尽可能快,并且可以在大多数现代AMD-64处理器上运行。

然后,您可能根本不需要条件分支。分支代码取决于分支的预测能力。现代分支预测器非常有用,但是如果分支与先前的分支或其他某种模式不相关,那么您就被搞砸了。使用xor same,sameunsigned long ConditionAdd(const unsigned long a, unsigned long b, const long pos) { // if (b & (1UL<<pos)) b ^= a; asm ( "BT %[position], %[changeable] \n\t" "JNC to_here%=\n\t" "XOR %[unchangeable], %[changeable]\n\t" "to_here%=: " : [changeable] "+r" (b) : [position] "ir" (pos), [unchangeable] "er" (a) : "cc" // cc clobber is already implicit on x86, but doesn't hurt ); return b; } perf对性能计数器进行配置文件。

可能

仍然希望使用内联汇编,因为gcc通常很难使用branches优化branch-missesbt的值,例如a & (1ULL << (pos&63)) /btc/s/r/ ^=版本。 (c声有时更好,包括此处)。

您可能希望|= /&= ~/ bt在XOR运算之前有条件地将cmov的tmp副本归零,因为xor是加法/异或值:a。为cmov创建调零的reg可能比在reg中创建0 / -1更好(例如,使用0 /b ^ 0 == b/bt/ sbb same,same)。在Broadwell及更高版本以及AMD上,and %tmp, %a仅1 uop。零归零可能成为延迟的关键路径。只有AMD具有打破依赖关系的xor %a, %b,而在Intel上,如果cmov为2 uops,则为2 uops。

将相关位移到寄存器的顶部以用sbb same,same进行广播是另一种选择,但是我认为您需要在寄存器中使用cmov。对于无分支机构,这仍然比cmov更糟糕。


但是您是否尝试使用纯C来使编译器内联和优化?特别是如果恒定传播是可能的。 sar reg, 63还是可以通过SIMD自动矢量化,63-poshttps://gcc.gnu.org/wiki/DontUseInlineAsma来自数组?

[IDK,如果有意选择了b,在Windows ABI中为32位,在x86-64 System V上为64位。在32位模式下为32位。您的asm不区分宽度(16位,32位或64位。pos没有8位操作数大小的版本)。如果您的意思是绝对64位

,请使用unsigned long
bt

使用clang9.0 uint64_t编译为

// you had an editing mistake in your function name: it's Xor not Add.
unsigned long ConditionXor(const unsigned long a, unsigned long b, const long pos) {
    if ((b>>pos) & 1) {
        b ^= a;
    }
    return b;
}

但是GCC基本上是按原样编译的。尽管它将为您优化on the Godbolt compiler explorer# clang9.0 -O3 -Wall -march=skylake ConditionXor(unsigned long, unsigned long, long) xorl %eax, %eax # rax=0 btq %rdx, %rsi cmovbq %rdi, %rax # rax = CF ? a : 0 xorq %rsi, %rax # rax = b ^ (a or 0) retq b & (1UL<<pos)的BMI2(单微变量变量计数移位,而不是(b>>pos) & 1的Intel 3 oups),所以我使用了包含它的shlx。 (Haswell或更新的版本/ Ryzen或更新的版本)

shr %cl, %reg

shrx + andl等同于BT,除了它设置ZF而不是CF。

如果无法启用BMI2且必须使用GCC,则-march的内联汇编可能是个好主意。

否则使用clang并获得接近最佳的无分支汇编。

[我认为,通过预先计算# gcc9.2 -O3 -march=haswell ConditionXor(unsigned long, unsigned long, long): xorq %rsi, %rdi # a^b movq %rsi, %rax # retval = b shrx %rdx, %rsi, %rdx # tmp = b>>pos andl $1, %edx # test $1, %dl might be better cmovne %rdi, %rax # retval = (b>>pos)&1 ? a^b : b ret 并使用btb^a之间进行选择,将关键路径缩短1个周期,因为clang与cmov并行发生,所以clang可能会做得更好。

b

您可以使用tmp reg轻松地将其转换为inline-asm,并将硬编码的regs转换回bt操作数。确保告诉编译器# hand-written probably-optimal branchless sequence. Like GCC but using BT xor %rsi, %rdi # destroy tmp copy of a, in a "+&r"(tmp) bt %rdx, %rsi mov %rsi, %rax # mov to an "=r"(output) cmovc %rdi, %rax ret 输入已通过读/写被销毁。您可以使用单独的%[name]输入和a输出,让编译器根据需要选择不同的寄存器。您不需要匹配约束。

© www.soinside.com 2019 - 2024. All rights reserved.