为什么 x86 设计者(或其他 CPU 架构)决定不包含它?它是一个逻辑门,可用于构建其他逻辑门,因此它作为单条指令快速。而不是链接not
和and
指令(两者都是从创建的nand
),为什么没有nand
指令?。
为什么现代 CPU 中没有“nand”指令?
但一般来说,现代 CPU 的构建是为了匹配编译器的自动代码生成,并且很少需要按位 NAND。按位 AND 和 OR 更常用于操作数据结构中的位域。事实上,SSE 有 AND-NOT 但没有 NAND。
每条指令在解码逻辑中都有成本,并消耗一个可用于其他用途的操作码。特别是在像 x86 这样的可变长度编码中,您可能会用完短操作码而不得不使用更长的操作码,这可能会减慢所有代码的速度。
这种 ALU 函数的成本是
1) 执行功能本身的逻辑
2) 从所有 ALU 函数中选择这个函数结果而不是其他函数的选择器
3)在指令集中有这个选项的成本(并且没有一些其他有用的功能)
我同意你的观点,1)成本非常小。然而,2) 和 3) 成本几乎与功能无关。我认为在这种情况下,3)成本(指令中占用的位)是没有这个特定指令的原因。对于 CPU/架构设计师来说,指令中的位是非常稀缺的资源。
把它转过来——首先看看为什么 Nand 在硬件逻辑设计中很受欢迎——它在那里有几个有用的属性。然后询问这些属性是否仍然适用于 CPU 指令......
TL/DR - 他们没有,所以使用 And、Or 或 Not 没有缺点。
硬连线 Nand 逻辑的最大优势是速度,通过减少电路输入和输出之间的逻辑电平(晶体管级)的数量获得。在 CPU 中,时钟速度由更复杂的运算(如加法)的速度决定,因此加快 AND 运算不会使您能够提高时钟频率。
而且您需要组合其他指令的次数非常少 - 足以让 Nand 真的无法在指令集中获得空间。
我想同意 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 大小的数据类型,所有事物都被简单地处理为 long(因此是 and l而不是l),因为这似乎是我的编译器的“本机”位宽。
中间有mov
s 的事实只是因为它eax
是包含函数返回值的寄存器。通常,您只需在edi
通用寄存器中计算以计算结果。
对于 64 位,它是相同的 - 只是使用“quad”(因此,尾随q
)单词和rax
/rsi
而不是eax
/ edi
。
似乎对于 128 位或更大的操作数,英特尔并不关心实现“非”操作;相反,编译器生成一个全1
寄存器(寄存器与自身的自我比较,结果与vdcmpeqd
指令一起存储在寄存器中),然后xor
就是那个。
简而言之:通过使用多条基本指令实现复杂的操作,您不一定会减慢操作速度——如果一条指令不能更快地完成多条指令的工作,那么它根本没有任何优势。