切换特定位
所以我已经看到了诸如在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
在btc
比xor
慢的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位移位然后cdqe
。 num ^= 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