让我们有int i
和char c
。
当使用i ^= c
时,编译器将使用c
的最低字节XOR i
,并将代码转换为单处理器指令。
当我们需要使用c
的最高字节的XOR i
时,我们可以这样做:
i ^= c << ((sizeof(i) - sizeof(c)) * 8)
但编译器将生成两个指令:XOR
和BIT-SHIFT
。
有没有办法用char
的最高字节对int
进行异或,这将被转换为C ++中的单处理器指令?
如果您对系统的字节顺序有信心,例如通过检查系统上的__BYTE_ORDER__
或等效宏,您可以执行以下操作:
#if // Somehow determing if little endian, so biggest byte at the end
*(&reinterpret_cast<char&>(i) + sizeof i - 1) ^= c
#else
// Is big endian, biggest byte at the beginning
reinterpret_cast<char&>(i) ^= c
#endif
不要假设编译器会使用上面的代码生成一个shift。大多数现代编译器比这更聪明:
编译器对这种简单的算术和按位运算非常聪明。他们不这样做只是因为他们不能,因为这些架构上没有这样的指示。对于很少使用的操作,不值得浪费宝贵的操作码空间。无论如何,大多数操作都在整个寄存器中完成,并且仅对寄存器的一部分进行操作对于CPU来说是非常低效的,因为无序执行或寄存器重命名单元将需要更加努力地工作。这就是为什么x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register或者为什么修改x86中寄存器的低部分(如AL或AX)可能比修改整个RAX要慢的原因。 INC
can also be slower than ADD 1
because of the partial flag update
也就是说,有一些架构可以在像ARM这样的单个指令中组合SHIFT和XOR,因为ARM设计人员将预编码和移位部分的大部分指令编码用于少数寄存器。但同样,你的前提是错误的,因为事物可以在一条指令中执行并不意味着它会更快。现代CPU非常复杂,因为每条指令都有不同的延迟,吞吐量和执行端口数。例如,如果CPU可以并行执行4对SHIFT-then-XOR,那么显然它会比另一个可以顺序运行4个单SHIFT-XOR指令的CPU更快,前提是时钟周期相同
这是一个非常典型的XY problem,因为你认为这只是错误的做法。对于需要完成数千,数百万次或更多次的操作,那就是GPU或SIMD unit的工作
例如,这是Clang编译器为一个循环发出的循环使用i
在x86 CPU上使用c
对AVX-512的顶部字节进行异或运算
vpslld zmm0, zmm0, 24
vpslld zmm1, zmm1, 24
vpslld zmm2, zmm2, 24
vpslld zmm3, zmm3, 24
vpxord zmm0, zmm0, zmmword ptr [rdi + 4*rdx]
vpxord zmm1, zmm1, zmmword ptr [rdi + 4*rdx + 64]
vpxord zmm2, zmm2, zmmword ptr [rdi + 4*rdx + 128]
vpxord zmm3, zmm3, zmmword ptr [rdi + 4*rdx + 192]
通过这样做,它只需2条指令即可实现16次SHIFT-XOR。想象一下这有多快。这就是为什么所有高性能架构都有某种SIMD,它更容易快速完成,而不是无用的SHIFT-XOR指令。即使在具有单指令SHIFT-XOR的ARM上,编译器也会足够聪明地知道SIMD比一系列eor rX, rX, rY, lsl #24
更快
shl v3.4s, v3.4s, 24
shl v2.4s, v2.4s, 24
shl v1.4s, v1.4s, 24
shl v0.4s, v0.4s, 24
eor v3.16b, v3.16b, v7.16b
eor v2.16b, v2.16b, v6.16b
eor v1.16b, v1.16b, v4.16b
eor v0.16b, v0.16b, v5.16b
Here's a demo for the above snippets
在多核中并行运行时,速度会更快。 GPU还能够执行非常高级别或并行性,因此现代密码术和强烈的数学问题通常在GPU上完成。它可以比使用SIMD的通用CPU更快地破解密码或加密文件