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


52

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


20
您对nand指令有什么用例?x86设计师可能从未发现任何东西
PlasmaHH

16
ARM的BIC指令为a & ~b。Arm Thumb-2的ORN指令为~(a | b)。ARM非常现代。在CPU指令集中编码一条指令会产生成本。因此,只有最“有用”的方法才可以进入ISA。
尤金(Eugene Sh)。

24
@Amumu我们也可以得到~(((a << 1) | (b >> 1)) | 0x55555555)指示。目的是~(((a << 1) | (b >> 1)) | 0x55555555)可以将其翻译成一条指令,而不是6条。那么,为什么不呢?
user253751 '17

11
@Amumu:那不是用例,也是〜不是!用例是该指令为何有用以及可以在何处应用的令人信服的原因。您的推理就像是说“该指令应该在这里以便可以使用”,但问题是“为此使用什么非常重要,以至于浪费资源”。
PlasmaHH

4
我已经进行了45年的编程工作,编写了一些编译器,并在可用时使用了一些奇怪的逻辑运算符,例如IMP,但是我从来没有用过NAND运算符或指令。
user207421

Answers:


62

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这样的可变长度编码中,您可能会用完短的操作码,而不得不使用更长的操作码,这可能会减慢所有代码的速度。


5
@supercat AND-NOT通常用于关闭位设置变量中的位。例如if(windowType & ~WINDOW_RESIZABLE) { ... do stuff for variable-sized windows ... }
adib

2
@adib:是的。“与非”的一个有趣特征是,与“按位非”运算符[〜]不同,结果大小无关紧要。如果foo是uint64_t,则该语句foo &= ~something;有时可能会清除比预期更多的位,但是如果有&~=运算符,则可以避免此类问题。
超级猫

6
@adib如果WINDOW_RESIZABLE为常数,则优化器应~WINDOW_RESIZABLE在编译时求值,因此这只是运行时的AND。
alephzero

4
@MarkRansom:不,因果关系从计算历史来看是完全正确的。设计针对编译器而不是人类汇编程序员进行了优化的CPU的这种现象是RISC运动的一部分(尽管RISC运动本身不仅限于此方面)。专为编译器设计的CPU包括ARM和Atmel AVR。在90年代末和00年代初,人们雇用了编译器作者和OS程序员来设计CPU指令集
slebetman

3
如今,与RAM访问相比,寄存器到寄存器的操作基本上是免费的。实施冗余指令会花费CPU中的芯片面积。因此,通常只有按位“或”和按位“与”的一种形式,因为添加按位补码寄存器-寄存器操作几乎不会减慢任何速度。
nigel222 '17

31

这种ALU功能的成本为

1)本身执行功能的逻辑

2)选择该函数结果的选择器,而不是所有ALU函数中的其他选择器

3)在指令集中拥有此选项的成本(并且没有其他有用的功能)

我同意您的看法:1)费用很小。但是2)和3)的成本几乎与功能无关。我认为在这种情况下,3)成本(指令中占用的位)是没有此特定指令的原因。对于CPU /体系结构设计人员而言,指令中的位是非常稀缺的资源。


29

转过来-首先看看为什么Nand在硬件逻辑设计中很受欢迎 -它在那里具有几个有用的属性。然后询问这些属性是否仍适用于CPU指令...

TL / DR-他们没有,因此使用And,Or或Not不会有不利的影响。

硬连线的Nand逻辑的最大优势是速度,它通过减少电路的输入和输出之间的逻辑电平(晶体管级)数量而获得。在CPU中,时钟速度由复杂得多的运算(例如加法)的速度决定,因此加快AND运算将不会使您提高时钟速率。

而且,您需要组合其他指令的次数几乎没有了-足够使Nand确实不会在指令集中获得其空间。


1
在不需要输入隔离的情况下,在硬件上“非”似乎很便宜。早在1977年,我为父母的拖车设计了一个转向信号控制器,每个灯使用两个晶体管和两个二极管来执行“异或”功能[左灯== xor(左信号,刹车);右灯== xor(右信号,制动)],每个灯实际上是两个或一个非接线功能。我还没有看到在LSI设计中使用这样的技巧,但是我认为在TTL或NMOS中,如果任何馈入输入都具有足够的驱动能力,这样的技巧可以节省电路。
超级猫

12

我想在这里与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指令一起存储在寄存器中),然后xors生成。

简而言之:通过使用多个基本指令来实现复杂的操作,您不必减慢操作的速度–如果一条指令执行速度较慢,那么执行多个指令就没有任何好处。


10

首先,不要混淆按位和逻辑运算。

按位操作通常用于设置/清除/切换/检查位域中的位。这些操作都不要求nand(“而不是”,也称为“位清除”更有用)。

大多数现代编程语言中的逻辑运算都是使用短路逻辑进行评估的。因此,通常需要基于分支的方法来实现它们。即使编译器可以确定短路与完全评估对程序行为没有影响,但逻辑运算的操作数通常也不具有使用逐位asm运算实现表达式的便捷形式。


10

NAND通常不会直接实现,因为隐式地具有AND指令使您能够在NAND条件下跳转。

在CPU中执行逻辑运算通常会在标志寄存器中设置位。

大多数标志寄存器都有一个零标志。如果逻辑运算的结果为零,则设置零标志,否则将其清除。

大多数现代CPU都有跳转指令,如果设置了零标志,该指令将跳转。它们还具有一个指令,如果未设置零标志,则该指令会跳转。

AND和NAND是互补的。如果“与”运算的结果为零,那么“与非”运算的结果为1,反之亦然。

因此,如果您希望在两个值的NAND为true时进行跳转,则只需执行AND操作即可,如果设置了零标志,则进行跳转。

因此,如果您希望如果两个值的NAND为假,则进行跳转,则只需执行与运算即可,如果清除了零标志,则进行跳转。


确实-条件跳转指令的选择使您可以为整个操作类选择反相和同相逻辑,而不必为每个操作单独实现。
克里斯·斯特拉顿

这应该是最好的答案。零标志运算使NAND在逻辑运算中变得多余,因为AND + JNZ和AND + JZ本质上是短路的/逻辑AND和NAND,两者都占用相同数量的操作码。
Lie Ryan

4

仅仅因为便宜的东西并不意味着它具有成本效益

如果我们不理会您的论点,则会得出结论,CPU应该主要由数百种NOP指令组成-因为它们是最便宜的实现。

或将其与金融工具进行比较:您会因为可以而购买1美元的债券,收益率为0.01%?不,您宁愿省下这些钱,直到您有足够的能力购买具有更好回报的10美元债券。CPU上的有机硅预算也是如此:有效地削减许多廉价但无用的NAND之类的运算,并将节省下来的晶体管放入更昂贵但真正有用的方式中。

没有种族争夺尽可能多的机会。正如RISC vs CISC证明了Turing从一开始就知道的那样:少即是多。实际上,尽可能少操作是更好的选择。


nop不能实现所有其他逻辑门,但可以nandnor可以有效地重新创建在CPU中用软件实现的任何指令。如果我们采用RISC方法,那就是..
Amumu

@Amumu我想你混淆gateinstruction。门用来实现指令,而不是相反。NOP是指令,不是门。是的,CPU包含成千上万甚至数百万个NAND门,以实现所有指令。只是不是“ NAND”指令。
Agent_L

2
@Amumu不是RISC方法:)这是“使用最广泛的抽象”方法,在非常特定的应用程序之外,它不太有用。当然,nand一个门可以用来实现其他门。但您已经有了其他所有说明。使用nand指令重新实现它们会比较。而且,它们经常被用来容忍这一点,这与您精心挑选的具体示例不同,nand会产生较短的代码(不是更快的代码,只是更短)不同。但这是极为罕见的,其收益根本不值得付出代价。
a安

@Amumu如果使用您的方法,我们将没有位置编号。您可以简单地说什么呢?((((()))))而不是5的意思是什么?五只是一个特定的数字,这太过局限了-设置更为通用:P
卢安

@Agent_L是的,我知道Gates执行指令。nand实现所有门,因此nand可以隐式实现所有其他指令。然后,如果程序员有nand可用的指令,则可以在逻辑门中进行思考时发明自己的指令。从一开始我的意思就是,如果它是如此基础,为什么没有给出它自己的指令(即,解码器逻辑中的操作码),因此程序员可以使用这样的指令。当然,在得到答案后,现在我知道这取决于软件的使用情况。
Amumu

3

在硬件级别,基本逻辑运算不是nand或nor。取决于技术(或取决于您任意称为1和您称为0),nand或nor可以以非常简单的基本方式实现。

如果我们忽略“ nor”情况,则所有其他逻辑均由nand构成。但这不是因为有某种计算机科学证明可以使用and构造所有逻辑运算-原因是没有任何基本的方法可以构建xor或x等,而后者比从nand的方法更好。

对于计算机指令,情况有所不同。可以实现nand指令,例如,它比实现xor便宜一点。但是只有一点点,因为计算结果的逻辑与解码指令,移动操作数,确保仅计算一个运算并拾取结果并将其传递到正确位置的逻辑相比很小。每条指令执行需要一个周期,与加法运算一样,加法运算在逻辑上要复杂十倍。与xor相比,nand的节省是微不足道的。

那么重要的是典型代码实际执行的操作需要多少条指令。Nand不在常见请求操作列表的顶部。请求和/或不请求的情况更为常见。处理器和指令集设计人员将检查大量现有代码,并确定不同的指令将如何影响该代码。他们最有可能发现,添加nand指令将减少运行以运行典型代码的处理器指令的数量,而用nand替换某些现有指令将增加执行的指令数量。


2

仅仅因为NAND(或NOR)可以用组合逻辑实现所有门,就不能以相同的方式转化为高效的按位运算符。要仅使用NAND运算(其中c = a AND b)来实现AND,则必须具有c = a NAND b,然后b = -1,然后c = c NAND b(对于NOT)。基本逻辑按位运算是AND,OR,EOR,NOT,NAND和NEOR。涉及的内容不多,而且前四个通常都是内置的。在组合逻辑中,基本逻辑电路仅受可用门数的限制,这完全是一种不同的球类游戏。听起来像您真正想要的那样,可编程门阵列中可能的互连数量确实是非常大的。某些处理器确实确实内置有门阵列。


0

您不仅仅因为它具有功能上的完整性就实现了逻辑门,尤其是在其他逻辑门本机可用的情况下。您可以实现编译器最常使用的功能。

很少需要NAND,NOR和XNOR。除了经典的按位运算符AND,OR和XOR外,只有ANDN(~a & b)–不是NAND(~(a & b)))才有实际用途。如果有的话,CPU应该实现这一点(确实有些CPU确实实现了ANDN)。

为了解释ANDN的实用性,假设您有一个使用许多位的位掩码,但是您只对其中的一些感兴趣,它们如下:

enum my_flags {
    IT_IS_FRIDAY = 1,
    ...
    IT_IS_WARM = 8,
    ...
    THE_SUN_SHINES = 64,
    ...
};

通常,您要检查位掩码中您感兴趣的位是否

  1. 他们都准备好了
  2. 至少设置了一个
  3. 至少有一个未设置
  4. 没有设置

让我们开始收集您感兴趣的部分:

#define BITS_OF_INTEREST (IT_IS_FRIDAY | IT_IS_WARM | THE_SUN_SHINES)

1.设置所有感兴趣的位:按位ANDN +逻辑非

假设您想知道是否已设置所有感兴趣的位。您可以将其视为(my_bitmask & IT_IS_FRIDAY) && (my_bitmask & IT_IS_WARM) && (my_bitmask & THE_SUN_SHINES)。但是通常您会将其折叠成

unsigned int life_is_beautiful = !(~my_bitmask & BITS_OF_INTEREST);

2.设置至少一个感兴趣的位:按位与

现在,假设您想知道是否设置了至少一位兴趣。您可以将其视为(my_bitmask & IT_IS_FRIDAY) || (my_bitmask & IT_IS_WARM) || (my_bitmask & THE_SUN_SHINES)。但是通常您会将其折叠成

unsigned int life_is_not_bad = my_bitmask & BITS_OF_INTEREST;

3.至少设置感兴趣的一位:按位ANDN

现在,假设您想知道是否没有设置至少一个感兴趣的位。您可以将其视为!(my_bitmask & IT_IS_FRIDAY) || !(my_bitmask & IT_IS_WARM) || !(my_bitmask & THE_SUN_SHINES)。但是通常您会将其折叠成

unsigned int life_is_imperfect = ~my_bitmask & BITS_OF_INTEREST;

4.未设置感兴趣的位:按位与+逻辑非

现在假设您想知道是否没有设置所有感兴趣的位。您可以将其视为!(my_bitmask & IT_IS_FRIDAY) && !(my_bitmask & IT_IS_WARM) && !(my_bitmask & THE_SUN_SHINES)。但是通常您会将其折叠成

unsigned int life_is_horrible = !(my_bitmask & BITS_OF_INTEREST);

这些是在位掩码上执行的常见操作,以及经典的按位OR和XOR。我认为虽然是一门语言(这不是一个CPU),应包括按位NAND,NOR和XNOR运营商(其标志是~&~|~^),尽管很少使用。但是,我不会在语言中包括ANDN运算符,因为它不是可交换的(a ANDN b与相同b ANDN a)–最好写成~a & b而不是a ANDN b,前者更清楚地显示出运算的不对称性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.