[我正在尝试做一些内联汇编,以在输入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;
}
没有-O3,总是在编译时给出错误。
您为编译器选择了bt
源操作数的内存。在禁用优化的情况下,它会这样做,因此结果不会合并。 bt
或bt $imm, r/m
是唯一可编码的形式。 (在Intel语法中,如手册bt %reg, r/m
或bt 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
[不要忘记允许"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,same
和unsigned 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-misses
或bt
的值,例如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-pos
,https://gcc.gnu.org/wiki/DontUseInlineAsm和a
来自数组?
[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
并使用bt
在b^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
输出,让编译器根据需要选择不同的寄存器。您不需要匹配约束。