현대 CPU에서 왜``nand ''명령이 없는가?


52

x86 디자이너 (또는 다른 CPU 아키텍처)도 왜 포함하지 않기로 결정 했습니까? 다른 논리 게이트를 빌드하는 데 사용할 수있는 논리 게이트이므로 단일 명령으로 빠릅니다. 연결 notand지시 (둘 다에서 생성됨 nand)가 아니라 왜 nand지시가 없습니까?


20
낸드 명령에 어떤 유스 케이스가 있습니까? 아마도 x86 디자이너는
아무것도

16
ARM에는 BIC명령어가 a & ~b있습니다. Arm Thumb-2의 ORN명령어는 ~(a | b)입니다. ARM은 꽤 현대적입니다. CPU 인스트럭션 세트의 인스트럭션 인코딩에는 비용이 듭니다. 따라서 가장 "유용한"사람 만이 ISA로 나아가고 있습니다.
유진 Sh.

24
@Amumu 우리도 ~(((a << 1) | (b >> 1)) | 0x55555555)지시를 받을 수있었습니다 . 목적은 ~(((a << 1) | (b >> 1)) | 0x55555555)6이 아닌 단일 명령어로 번역 될 수 있도록하기위한 것 입니다.
user253751

11
@ Amumu : 유스 케이스가 아니며 ~도 아닙니다!. 유스 케이스는 해당 명령이 유용한 이유와 적용 가능한 위치입니다. 당신의 추론은 "사용할 수 있도록 지시가 있어야한다"고 말하는 것과 같지만 문제는 "무엇을 위해 그것을 사용해야하는지가 자원을 소비하는 데 유용하다"는 것입니다.
PlasmaHH

4
나는 45 년 동안 프로그래밍을 해왔고, 몇 개의 컴파일러를 작성했으며, IMP와 같은 가용 한 경우 좀 더 복잡한 논리 연산자를 사용했지만 NAND 연산자 나 명령어를 사용한 적이 없었습니다.
user207421

답변:


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에는 NAND가 아닌 AND-NOT가 있습니다.

모든 명령어는 디코딩 로직에 비용이 들며 다른 용도로 사용될 수있는 opcode를 소비합니다. 특히 x86과 같은 가변 길이 인코딩의 경우 짧은 opcode가 부족하고 더 긴 opcode를 사용해야하므로 잠재적으로 모든 코드가 느려질 수 있습니다.


5
@supercat AND-NOT는 일반적으로 비트 세트 변수에서 비트를 끄는 데 사용됩니다. 예if(windowType & ~WINDOW_RESIZABLE) { ... do stuff for variable-sized windows ... }
adib

2
@adib : 예. "and-not"의 흥미로운 특징은 "bitwise not"연산자와 달리 결과 크기가 중요하지 않다는 것입니다. 경우 foouint64_t이며, 문은 foo &= ~something;때로는 의도 한 것보다 더 많은 비트를 지울 수 있지만이 있다면 &~=운영자 이러한 문제를 피할 수 있습니다.
supercat

6
@adib if WINDOW_RESIZABLE상수 인 경우 옵티마이 저는 ~WINDOW_RESIZABLE컴파일 타임에 평가해야 하므로 런타임시 AND 일뿐입니다.
alephzero

4
@MarkRansom : 아니요. 원인과 결과는 컴퓨팅 기록에서 완전히 정확합니다. 휴먼 어셈블리 프로그래머 대신 컴파일러에 최적화 된 CPU를 설계하는 이러한 현상은 RISC 운동의 일부였습니다 (하지만 RISC 운동 자체는 해당 측면보다 더 넓습니다). 컴파일러 용으로 설계된 CPU에는 ARM 및 Atmel AVR이 포함됩니다. 후반 90 년대 초반 00 년대에 사람들은 CPU 명령어 세트를 디자인 컴파일러 작가와 OS 프로그래머 고용
slebetman

3
요즘 등록-등록 작업은 RAM 액세스에 비해 본질적으로 무료입니다. 중복 명령어를 구현하려면 CPU의 실리콘 부동산 비용이 발생합니다. 따라서 비트 단위의 레지스터 레지스터 연산을 추가해도 속도가 느려지지 않기 때문에 일반적으로 비트 단위 OR 및 비트 단위 AND의 한 가지 형식 만 있습니다.
nigel222

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"기능을 수행하기 위해 조명 당 2 개의 트랜지스터와 2 개의 다이오드를 사용하여 부모 트레일러 용 턴 시그널 컨트롤러를 설계했습니다. [왼쪽 램프 == xor (왼쪽 신호, 브레이크); 오른쪽 램프 == xor (오른쪽 신호, 브레이크)], 본질적으로 각 조명에 대해 두 가지 기능을 배선 또는 연결하지 않습니다. LSI 설계에 이러한 트릭이 사용되는 것을 보지 못했지만 TTL 또는 NMOS에서는 입력을 공급하는 것이 무엇이든 적절한 드라이브 기능을 제공하는 경우 그러한 트릭은 회로를 절약 할 수 있다고 생각합니다.
supercat

12

나는 Brian, Wouter와 pjc50에 동의하고 싶습니다.

또한 범용, 특히 CISC 프로세서에서 명령어는 처리량이 모두 같지 않다는 점을 추가하고 싶습니다. 복잡한 작업은 단순히 더 많은주기를 필요로 할 수 있습니다.

X86 : AND( "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 크기 이하의 데이터 유형의 경우 모든 것이 단순히 긴 것으로 처리되므로 (따라서 l이 아닌 l ) 컴파일러의 "네이티브"비트 폭이므로 보입니다.

거기에 있다는 사실은 mov사실로 인해 아니라 사이에요 eax함수의 반환 값이 들어있는 레지스터입니다. 일반적 edi으로 범용 레지스터에서 계산하여 결과를 계산합니다.

단지 "쿼드"(따라서, 후행 - 64 비트의 경우, 동일의 q) 단어 및 rax/ rsi대신 eax/ edi.

128 비트 피연산자 이상에서는 인텔이 "비"연산을 구현하지 않았던 것 같습니다. 대신, 컴파일러는 모든 1레지스터 (자체와 레지스터의 자체 비교, vdcmpeqd명령어 와 함께 레지스터에 저장된 결과 )를 생성 xor합니다.

간단히 말해서 : 여러 기본 명령으로 복잡한 작업을 구현하면 작업 속도가 느려질 필요는 없습니다. 여러 명령이 더 빠르지 않은 경우 여러 명령을 수행하는 명령 하나만 있으면 이점이 없습니다.


10

우선 비트 및 논리 연산을 혼동하지 마십시오.

비트 단위 연산은 일반적으로 비트 필드에서 비트를 설정 / 삭제 / 토글 / 확인하는 데 사용됩니다. 이들 연산 중 어느 것도 낸드를 필요로하지 않는다 ( "비트 클리어"라고도 알려진 "and not"이 더 유용하다).

대부분의 최신 프로그래밍 언어의 논리 연산은 단락 논리를 사용하여 평가됩니다. 따라서 일반적으로이를 구현하기위한 지점 기반 접근 방식이 필요합니다. 컴파일러가 단락 대 완전한 평가가 프로그램 동작과 차이가 없다고 판단 할 수 있더라도, 논리 연산의 피연산자는 일반적으로 비트 단위 asm 연산을 사용하여 표현식을 구현하기에 편리한 형태가 아닙니다.


10

AND 명령을 내재적으로 사용하면 NAND 조건으로 이동할 수 있으므로 NAND는 종종 직접 구현되지 않습니다.

CPU에서 논리 연산을 수행하면 종종 플래그 레지스터에 비트가 설정됩니다.

대부분의 플래그 레지스터에는 ZERO 플래그가 있습니다. 논리 연산 결과가 0이면 0 플래그가 설정되고 그렇지 않으면 해제됩니다.

대부분의 최신 CPU에는 제로 플래그가 설정되어 있으면 점프하는 점프 명령이 있습니다. 또한 제로 플래그가 설정되어 있지 않으면 점프하는 istruction이 있습니다.

AND와 NAND는 보수입니다. AND 연산 결과가 0이면 NAND 연산 결과는 1이고 그 반대도 마찬가지입니다.

따라서 두 값의 NAND가 true 인 경우 ot jump를 원하면 AND 연산을 수행하고 zero flag가 설정된 경우 jump하십시오.

따라서 두 값의 NAND가 거짓이면 OT 점프를 원한다면 AND 연산을 수행하고 제로 플래그가 클리어되면 점프하십시오.


실제로-조건부 점프 명령을 선택하면 개별적으로 각각의 선택을 구현하지 않고도 전체 작업 클래스에 대해 반전 및 비 반전 논리를 선택할 수 있습니다.
Chris Stratton

이것이 가장 좋은 대답이었습니다. 제로 플래그 연산은 AND + JNZ 및 AND + JZ가 본질적으로 단락 / 논리적 AND이며 NAND이며, 둘 다 동일한 수의 opcode를 사용하므로 NAND가 논리 연산에 불필요합니다.
Lie Ryan

4

저렴한 것이라도 비용 효율적인 것은 아닙니다 .

우리가 논란의 여지가 있지만, CPU는 구현하기에 가장 저렴하기 때문에 CPU가 수백 가지의 NOP 명령으로 구성되어야한다는 결론에 도달 할 것입니다.

또는 금융 상품과 비교해보십시오. 가능한 한 0.01 %의 수익을 가진 $ 1 채권을 구매 하시겠습니까? 아닙니다. 더 나은 수익으로 10 달러의 채권을 구매할 수있을 때까지 그 돈을 절약 할 수 있습니다. CPU의 실리콘 예산도 마찬가지입니다. NAND와 같이 값 싸지 만 쓸모없는 연산을 많이 사용하는 것이 효과적이며, 저장된 트랜지스터를 더 비싸지 만 실제로 유용한 방법으로 넣습니다.

가능한 한 많은 작전을 가진 인종은 없습니다. RISC와 CISC는 튜링이 처음부터 알고있는 것을 증명 했으므로 적은 것이 많을수록 좋습니다. 실제로 적은 수의 작전을 갖는 것이 좋습니다.


nop다른 모든 논리 게이트를 구현 할 수 없지만, nand또는 nor, 효과적으로 소프트웨어의 CPU에서 실행되는 모든 명령을 다시 만들 수 있습니다. 우리가 RISC 접근 방식을
취한다면

@Amumu 나는 당신이 혼합 있다고 생각 gate하고 instruction. 게이트는 다른 방법이 아닌 명령어를 구현하는 데 사용됩니다. NOP문이 아니라 명령입니다. 그리고 그렇습니다. CPU는 모든 명령을 구현하기 위해 수천 또는 수백만 개의 NAND 게이트를 포함합니다. "NAND"명령이 아닙니다.
Agent_L

2
@Amumu RISC 방식은 아닙니다. :) "가장 넓은 추상화 사용"방식으로, 특정 응용 프로그램 이외에서는 유용하지 않습니다. 물론, nand다른 게이트를 구현하는 데 사용할 수있는 하나의 게이트입니다. 그러나 당신은 이미 다른 모든 지시 사항을 가지고 있습니다. nand명령을 사용하여 다시 구현하면 속도느려집니다 . nand더 짧은 코드 ( 더 빠른 코드가 아니라 더 짧은 코드)를 생성하는 체리로 선택한 특정 예제와 달리 너무 자주 사용됩니다 . 그러나 그것은 극히 드문 일이며 그 이점은 단순히 비용이 들지 않습니다.
루안

@Amumu 우리가 당신의 접근 방식을 사용한다면, 우리는 위치 숫자를 가지지 않을 것입니다. ((((()))))5 대신 간단하게 말할 수있는 요점은 무엇입니까 ? 다섯은 하나의 특정 숫자 일 뿐이며 너무 제한적입니다. 세트가 훨씬 더 일반적입니다 : P
Luaan

@Agent_L 예, 게이트가 명령어를 구현한다는 것을 알고 있습니다. nand모든 게이트를 구현하므로 암시 적으로 nand다른 모든 명령을 구현할 수 있습니다. 그런 다음 프로그래머에게 nand사용 가능한 명령이 있으면 논리 게이트에서 생각할 때 자신의 명령을 발명 할 수 있습니다. 내가 처음부터 의미 한 것은 그것이 매우 기본적이라면 왜 자체 명령 (즉, 디코더 논리의 opcode)이 주어지지 않았기 때문에 프로그래머가 그러한 명령을 사용할 수 있다는 것입니다. 물론 답을 얻은 후에는 소프트웨어 사용에 따라 다릅니다.
Amumu

3

하드웨어 수준에서 기본 논리 연산은 nand 또는 nor입니다. 기술에 따라 (또는 임의로 1이라고 부르는 것과 0으로 부르는 것에 따라) 낸드 또는 아주 간단한 기본 방식으로 구현할 수 있습니다.

"nor"경우를 무시하면 다른 모든 논리는 nand로 구성됩니다. 그러나 모든 논리 연산을 구성 할 수 있다는 컴퓨터 과학적 증거가 있기 때문에 xor를 만드는 기본 방법 이 없기 때문에 낸드에서 만드는 것보다 더 좋은 방법 이 없기 때문입니다 .

컴퓨터 지침의 경우 상황이 다릅니다. 낸드 명령어가 구현 될 수 있으며, 예를 들어 xor를 구현하는 것보다 약간 저렴합니다. 그러나 결과를 계산하는 로직이 명령어를 디코딩하고 피연산자를 이동하는 로직과 비교하여 작기 때문에 하나의 연산 만 계산되고 결과를 선택하여 올바른 위치로 전달합니다. 각 명령어는 논리적으로 10 배 더 복잡한 추가와 마찬가지로 한주기를 실행합니다. 낸드 대 xor의 절약은 무시할 만하다.

중요한 것은 전형적인 코드로 실제로 수행되는 작업에 필요한 명령 수 입니다. Nand는 일반적으로 요청되는 작업 목록의 맨 위에 없습니다. 요청되거나 요청되지 않는 것이 훨씬 일반적입니다. 프로세서 및 명령어 세트 디자이너는 많은 기존 코드를 검사하고 다른 명령어가 해당 코드에 미치는 영향을 결정합니다. nand 명령어를 추가하면 일반적인 코드를 실행하기 위해 실행되는 프로세서 명령어 수가 거의 줄어들지 않으며 일부 기존 명령어를 nand로 바꾸면 수행되는 명령어 수가 늘어날 것입니다.


2

NAND (또는 NOR)가 모든 게이트를 조합 논리로 구현할 수 있기 때문에 동일한 방식으로 효율적인 비트 연산자로 변환하지 않습니다. NAND 연산 만 사용하여 AND를 구현하려면 (c = a AND b) c = a NAND b, b = -1, c = c NAND b (Not NOT)가 있어야합니다. 기본 논리 비트 단위 연산은 AND, OR, EOR, NOT, NAND 및 NEOR입니다. 그것은 다룰 것이 많지 않으며 처음 4 개는 일반적으로 어쨌든 내장되어 있습니다. 조합 논리에서 기본 논리 회로는 사용 가능한 게이트 수에 의해서만 제한되며 이는 완전히 다른 볼 게임입니다. 프로그래머블 게이트 어레이에서 가능한 상호 연결의 수는 실제로 다음과 같이 들릴 것입니다. 일부 프로세서에는 실제로 게이트 어레이가 내장되어 있습니다.


0

논리 게이트는 기능적으로 완벽하기 때문에 특히 다른 논리 게이트가 기본적으로 사용 가능한 경우 논리 게이트를 구현하지 않습니다. 컴파일러가 가장 많이 사용하는 것을 구현합니다.

NAND, NOR 및 XNOR은 거의 필요하지 않습니다. 고전적인 비트 연산자 AND, OR 및 XOR 외에도 ~a & bNAND ( ~(a & b)) 가 아닌 ANDN ( ) 만이 실용적인 유용성을 갖습니다. 있는 경우 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 + 논리 NOT

관심있는 비트가 모두 설정되어 있는지 알고 싶다고 가정 해 봅시다. 당신은 그것을 다음과 같이 볼 수 있습니다 (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. 적어도 하나의 관심 비트가 설정됩니다 : 비트 AND

이제 적어도 하나의 관심 비트가 설정되어 있는지 알고 싶다고 가정 해 봅시다. 당신은 그것을 볼 수 있습니다 (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. 관심 비트가 설정되지 않았습니다 : 비트 AND + 논리 NOT

이제 모든 관심 비트가 설정 되어 있지 않은지 알고 싶다고 가정 해 봅시다 . 당신은 그것을 볼 수 있습니다 !(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 bb ANDN a~a & ba ANDN b

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.