为什么现代 CPU 中没有“nand”指令?

电器工程 中央处理器
2022-01-05 02:12:28

为什么 x86 设计者(或其他 CPU 架构)决定不包含它?它是一个逻辑门,可用于构建其他逻辑门,因此它作为单条指令快速。而不是链接notand指令(两者都是从创建的nand),为什么没有nand指令?。

4个回答

http://www.ibm.com/support/knowledgecenter/ssw_aix_61/com.ibm.aix.alangref/idalangref_nand_nd_instrs.htm:POWER有 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),因为这似乎是我的编译器的“本机”位宽。

中间有movs 的事实只是因为它eax是包含函数返回值的寄存器。通常,您只需在edi通用寄存器中计算以计算结果。

对于 64 位,它是相同的 - 只是使用“quad”(因此,尾随q)单词和rax/rsi而不是eax/ edi

似乎对于 128 位或更大的操作数,英特尔并不关心实现“非”操作;相反,编译器生成一个全1寄存器(寄存器与自身的自我比较,结果与vdcmpeqd指令一起存储在寄存器中),然后xor就是那个。

简而言之:通过使用多条基本指令实现复杂的操作,您不一定会减慢操作速度——如果一条指令不能更快地完成多条指令的工作,那么它根本没有任何优势。