我想在这里与Brian以及Wouter和pjc50保持一致。
我还想补充一点,在通用处理器(尤其是CISC处理器)上,指令的吞吐率并不完全相同–复杂的操作可能比简单的操作花费更多的周期。
考虑X86 :(AND
这是“与”操作)可能非常快。同样适用NOT
。让我们看一下反汇编:
输入代码:
#include <immintrin.h>
#include <stdint.h>
__m512i nand512(__m512i a, __m512i b){return ~(a&b);}
__m256i nand256(__m256i a, __m256i b){return ~(a&b);}
__m128i nand128(__m128i a, __m128i b){return ~(a&b);}
uint64_t nand64(uint64_t a, uint64_t b){return ~(a&b);}
uint32_t nand32(uint32_t a, uint32_t b){return ~(a&b);}
uint16_t nand16(uint16_t a, uint16_t b){return ~(a&b);}
uint8_t nand8(uint8_t a, uint8_t b){return ~(a&b);}
产生装配的命令:
gcc -O3 -c -S -mavx512f test.c
输出组件(缩短):
.file "test.c"
nand512:
.LFB4591:
.cfi_startproc
vpandq %zmm1, %zmm0, %zmm0
vpternlogd $0xFF, %zmm1, %zmm1, %zmm1
vpxorq %zmm1, %zmm0, %zmm0
ret
.cfi_endproc
nand256:
.LFB4592:
.cfi_startproc
vpand %ymm1, %ymm0, %ymm0
vpcmpeqd %ymm1, %ymm1, %ymm1
vpxor %ymm1, %ymm0, %ymm0
ret
.cfi_endproc
nand128:
.LFB4593:
.cfi_startproc
vpand %xmm1, %xmm0, %xmm0
vpcmpeqd %xmm1, %xmm1, %xmm1
vpxor %xmm1, %xmm0, %xmm0
ret
.cfi_endproc
nand64:
.LFB4594:
.cfi_startproc
movq %rdi, %rax
andq %rsi, %rax
notq %rax
ret
.cfi_endproc
nand32:
.LFB4595:
.cfi_startproc
movl %edi, %eax
andl %esi, %eax
notl %eax
ret
.cfi_endproc
nand16:
.LFB4596:
.cfi_startproc
andl %esi, %edi
movl %edi, %eax
notl %eax
ret
.cfi_endproc
nand8:
.LFB4597:
.cfi_startproc
andl %esi, %edi
movl %edi, %eax
notl %eax
ret
.cfi_endproc
如您所见,对于64位以下的数据类型,所有事情都被简单地处理了很长的时间(因此,and和l而不是l),因为看起来这是编译器的“本机”位宽。
mov
之间存在s的事实仅是由于eax
包含函数返回值的寄存器是这样的事实。通常,您只需要在edi
通用寄存器中进行计算就可以计算结果。
对于64位,这是相同的–只是带有“ quad”(因此,尾随q
)单词,并且用rax
/ rsi
代替eax
/ edi
。
似乎对于128位及更大的操作数,英特尔并不在乎实现“非”运算。取而代之的是,编译器生成一个全1
寄存器(寄存器与自身的自比较,结果与vdcmpeqd
指令一起存储在寄存器中),然后xor
s生成。
简而言之:通过使用多个基本指令来实现复杂的操作,您不必减慢操作的速度–如果一条指令执行速度较慢,那么执行多个指令就没有任何好处。