切换特定位

所以我已经看到了诸如在ith positon稍微切换一下的问题以及如何设置,清除和切换一个位? ,但我想知道是否有一个很好的方法在x86-64汇编中切换第i个位置?

我尝试用C语言编写并查看程序集,并不完全明白为什么有些东西存在。

C:

unsigned long toggle(unsigned long num, unsigned long bit) { num ^= 1 << bit; return num; } int main() { printf("%ld\n", toggle(100, 60)); return 0; } 

从GDB切换function组件:

  push rbp mov rbp, rsp mov QWORD PTR [rbp-0x8],rdi mov QWORD PTR [rbp-0x10],rsi mov rax, QWORD PTR [rbp-0x10] mov edx, 0x1 mov ecx, eax shl edx, cl mov eax, edx cdqe xor QWORD PTR [rbp-0x8],rax mov rax, QWORD PTR [rbp-0x8] pop rbp ret 

有人可以告诉我assembly级别的内容,以便我能更好地理解这一点,并在x86-64中编写自己的切换function吗?

我想知道在x86-64汇编中是否有一个很好的方法可以在第i个位置切换一下?

是的, x86的BTC (位测试和补充)指令正是如此 (以及将CF设置为该位的旧值),并在所有现代CPU上高效运行。

  • Intel SnB系列:1 uop,1c延迟,每时钟吞吐量2。 (Nehalem和更早:每个时钟1个)
  • Silvermont / KNL:1 uop,1c延迟,每时钟吞吐量1。
  • AMD Ryzen:2 uops,2c延迟,每时钟吞吐量2
  • AMD Bulldozer系列/ Jaguar:2 uops,2c延迟,每时钟吞吐量1
  • AMD K8 / K10:2 uops,2c延迟,每时钟吞吐量1

资料来源: Agner Fog的指令表和x86优化指南 。 另请参阅x86标记wiki中的其他性能链接。

 toggle: mov rax, rdi btc rax, rsi ret 

(如果你在C中正确地写了toggle )。

不要将btc与内存操作数一起使用:位串指令具有疯狂的CISC语义,其中位索引不限于由寻址模式选择的双字内。 (所以btc m,r是10 btc m,r Skylake每5c吞吐量一个)。 但是对于寄存器操作数,移位计数与变量计数移位完全相同。

不幸的是,gcc和clang错过了这个窥视孔优化,即使使用-march=haswell-mtune=intel 。 它甚至可以在AMD上使用,但它在英特尔上的效率更高。


重复使用具有多个输入的相同1ULL << bit

btcxor慢的AMD CPU上,值得在寄存器中生成掩码并使用xor 。 或者甚至在Intel CPU上,在内存中切换一点也是值得的。 (memory-destination xor比memory-destination btc )。

对于数组中的多个元素,请使用SSE2 pxor 。 您可以使用以下命令生成蒙版:

 pcmpeqd xmm0, xmm0 ; -1 all bits set psrlq xmm0, 63 ; 1 just a single bit set movd xmm1, esi psllq xmm0, xmm1 ; 1< 

我不太明白为什么会有一些东西存在。

所有垃圾都在那里,因为你编译时没有优化,因为你使用了一个有符号的int常量。


甚至不值得从-O0代码查看所有溢出/重新加载到内存。 如果你想要的代码不吸引,请使用-O3 -march=native编译。

另请参见如何从GCC /铿锵声组件输出中删除“噪音”? 和Matt Godbolt的CppCon2017谈话: “我的编译器最近为我做了什么? 解开编译器的盖子“是一个很好的介绍,看看编译器生成的asm。


使用signed int常量1 << bit解释了为什么gcc执行32位移位然后cdqenum ^= 1 << bit; 相当于

 int mask = 1; mask <<= bit; // still signed int num ^= mask; // mask is sign-extended to 64-bit here. 

在gcc -O3输出中,我们得到

  mov edx, 1 sal edx, cl # 1<rax xor rax, rdi 

如果我们写核心toggle

 uint64_t toggle64(uint64_t num, uint32_t bit) { num ^= 1ULL << bit; return num; } 

(Godbolt编译器资源管理器上的源+ asm)

gcc和clang仍然错过了使用btc ,但这并不可怕。 有趣的是,MSVC确实发现了btc窥视孔,但浪费了MOV指令:

 toggle64 PROC mov eax, edx btc rcx, rax mov rax, rcx ret 0 

使用uint64_t位可以避免额外的MOV。 这是不必要的,因为带有寄存器目的地的btc& 63屏蔽了索引。 高垃圾不是问题,但MSVC不知道这一点。

gcc和clang发出代码就像你期望的那样,但gcc通过在rdx生成1ULL <并且必须复制到rax来浪费MOV指令。

  ; clang output. mov eax, 1 mov ecx, esi shl rax, cl xor rax, rdi ret