x86-64位切换一个数的第n位
x86-64位切换一个数的第n位
我看到了类似于在第i个位置切换一个位和如何设置、清除和切换单个位?的问题,但我想知道在x86-64汇编中是否有一种好的方法来切换第i个位置的位?我尝试用C语言编写它,并查看汇编代码,但我不太明白为什么有些东西存在。\nC代码:\n
unsigned long toggle(unsigned long num, unsigned long bit) { num ^= 1 << bit; return num; } int main() { printf("%ld\n", toggle(100, 60)); return 0; }
\n从GDB中获取的Toggle函数汇编代码:\n
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
\n有人可以帮我解释一下汇编层面上发生了什么,这样我就能更好地理解,并自己编写x86-64的切换函数了吗?
x86-64位切换数字中的第n位的出现原因是一个用户在询问是否有一个好的方法在x86-64汇编语言中切换第i位的问题。解决方法是使用x86的BTC(Bit Test and Complement)指令来实现(同时将CF设置为位的旧值),该指令在所有现代CPU上运行效率高。
解决方法是使用以下代码:
toggle: mov rax, rdi btc rax, rsi ret
如果在C语言中正确地编写了toggle函数的话。
需要注意的是,不要在内存操作数上使用btc指令,因为位字符串指令具有疯狂的CISC语义,其中位索引不限于地址模式选择的双字内。但是对于寄存器操作数,移位计数与变量计数移位一样被掩码处理。
如果要重复使用相同的1ULL << bit和多个输入,则在AMD CPU上,btc比xor慢,值得在寄存器中生成掩码并使用xor。甚至在Intel CPU上,这也值得为了在内存中切换位(内存目标的xor要比内存目标的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<
然后在循环内部,对于在xmm1中的数据,可以使用以下代码来反转每个qword元素中的位:
pxor xmm1, xmm0 ; flip bit in each qword element
由于编译时没有进行优化,并且使用了有符号的int常量,所以出现了一些不必要的指令。如果想要得到更好的代码,可以使用`-O3 -march=native`编译选项。
如果将toggle函数正确编写为:
uint64_t toggle64(uint64_t num, uint32_t bit) { num ^= 1ULL << bit; return num; }
GCC和Clang仍然没有使用btc指令,但情况并不糟糕。有趣的是,MSVC确实发现了btc peephole,但浪费了一个MOV指令。使用uint64_t位避免了额外的MOV指令。
最后,需要注意的是在使用有符号的int常量`1 << bit`时,GCC会生成一个32位的移位操作,并且使用了cdqe来进行符号扩展。可以通过将代码编写为`num ^= 1ULL << bit;`来避免这个问题。
总之,通过使用x86的BTC指令,可以在x86-64汇编语言中高效地切换数字的第n位。同时,对于多个输入,可以使用SSE2的pxor指令。如果要获得更好的代码,可以使用优化选项进行编译。