¿Por qué no hay instrucciones 'nand' en las CPU modernas?

52

¿Por qué los diseñadores x86 (u otras arquitecturas de CPU también) decidieron no incluirlo? Es una compuerta lógica que se puede usar para construir otras compuertas lógicas, por lo que es rápida como una sola instrucción. En lugar de encadenar las instrucciones not y and (ambas se crean a partir de nand ), ¿por qué no hay una instrucción nand ?

    
pregunta Amumu

9 respuestas

61

enlace : POWER tiene NAND.

Pero, en general, las CPU modernas están diseñadas para compaginar la generación automatizada de códigos, y rara vez se requiere NAND bitwise. Bitwise AND y OR se usan más a menudo para manipular campos de bits en estructuras de datos. De hecho, SSE tiene AND-NOT pero no NAND.

Cada instrucción tiene un costo en la lógica de decodificación y consume un código de operación que podría usarse para otra cosa. Especialmente en codificaciones de longitud variable como x86, puede quedarse sin códigos de operación cortos y tiene que usar códigos más largos, lo que potencialmente ralentiza todo el código.

    
respondido por el pjc50
32

El costo de tales funciones de ALU es

1) la lógica que realiza la función en sí misma

2) el selector que selecciona el resultado de esta función en lugar de los demás de todas las funciones de ALU

3) el costo de tener esta opción en el conjunto de instrucciones (y no tener alguna otra función útil)

Estoy de acuerdo con usted en que 1) el costo es muy pequeño. El costo 2) y 3) sin embargo es casi independiente de la función. Creo que en este caso el 3) costo (los bits ocupados en la instrucción) fueron la razón para no tener esta instrucción específica. Los bits en una instrucción son un recurso muy escaso para un diseñador de arquitectura / CPU.

    
respondido por el Wouter van Ooijen
28

Gírelo: primero vea por qué Nand fue popular en el diseño de lógica de hardware - tiene varias propiedades útiles allí. Luego pregunte si esas propiedades aún se aplican en una instrucción de CPU ...

TL / DR: no lo hacen, por lo que no hay inconveniente en usar And, Or or Not en su lugar.

La mayor ventaja de la lógica Nand cableada fue la velocidad, obtenida al reducir el número de niveles lógicos (etapas de transistor) entre las entradas y salidas de un circuito. En una CPU, la velocidad del reloj está determinada por la velocidad de operaciones mucho más complejas como la adición, por lo que acelerar una operación AND no le permitirá aumentar la velocidad de reloj.

Y la cantidad de veces que necesitas combinar otras instrucciones es muy pequeña, lo suficiente como para que Nand no gane espacio en el conjunto de instrucciones.

    
respondido por el Brian Drummond
11

Me gustaría estar de acuerdo con Brian aquí, y con Wouter y pjc50.

También me gustaría agregar que, en general, los procesadores CISC y las instrucciones no tienen todos los mismos rendimientos; una operación complicada simplemente puede requerir más ciclos que una fácil.

Considere X86: AND (que es una operación "y") es probablemente muy rápido. Lo mismo ocurre con NOT . Veamos un poco de desmontaje:

Código de entrada:

#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);}

Comando para producir ensamblaje:

gcc -O3 -c -S  -mavx512f test.c

Conjunto de salida (acortado):

    .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

Como puede ver, para los tipos de datos de tamaño sub-64, las cosas se manejan simplemente como largos (por lo tanto, el l y no l ), ya que ese es el bitwidth "nativo" de mi compilador, como parece.

El hecho de que haya mov s en medio solo se debe al hecho de que eax es el registro que contiene el valor de retorno de una función. Normalmente, solo se calcula en el registro de propósito general edi para calcular con el resultado.

Para 64 bits, es lo mismo: solo con las palabras "quad" (por lo tanto, finales q ) y rax / rsi en lugar de eax / edi .

Parece que para operandos de 128 bits y más grandes, Intel no se preocupó de implementar una operación "no"; en cambio, el compilador produce un registro de todos- 1 (auto-comparación del registro consigo mismo, resultado almacenado en el registro con la instrucción vdcmpeqd ), y xor s eso.

En resumen: Al implementar una operación complicada con múltiples instrucciones elementales, no necesariamente se ralentiza la operación; simplemente no hay ventaja de tener una instrucción que haga el trabajo de varias instrucciones si no es más rápida.

    
respondido por el Marcus Müller
10

En primer lugar, no hay que confundir operaciones lógicas y en modo bit.

Las operaciones bitwise se usan generalmente para establecer / borrar / alternar / verificar bits en los campos de bits. Ninguna de estas operaciones requiere nand ("y no", también conocido como "borrar bits" es más útil).

Las operaciones lógicas en la mayoría de los lenguajes de programación modernos se evalúan utilizando la lógica de cortocircuito. Por lo general, se necesita un enfoque basado en sucursales para implementarlos. Incluso cuando el compilador puede determinar que el cortocircuito en comparación con la evaluación completa no hace ninguna diferencia en el comportamiento del programa, los operandos para las operaciones lógicas generalmente no están en una forma conveniente para implementar la expresión usando las operaciones asm en modo bit a bit.

    
respondido por el Peter Green
10

La NAND a menudo no se implementa directamente porque tener la instrucción AND implícitamente le da la posibilidad de saltar en una condición NAND.

La realización de una operación lógica en una CPU a menudo establece bits en un registro de bandera.

La mayoría de los registros de bandera tienen una bandera de CERO. El indicador cero se establece si el resultado de una operación lógica es cero y, de lo contrario, se borra.

La mayoría de las CPU modernas tienen una instrucción de salto que salta si se establece el indicador cero. También tienen un istruction que salta si no se establece el indicador cero.

Y y NAND son complementos. Si el resultado de una operación AND es cero, entonces el resultado de una operación NAND es 1 y viceversa.

Entonces, si desea saltar, si la NAND de dos valores es verdadera, simplemente realice la operación AND, y salte si se establece el indicador cero.

Por lo tanto, si desea saltar, si la NAND de dos valores es falsa, simplemente realice la operación AND y salte si el indicador de cero está despejado.

    
respondido por el user4574
4

El hecho de que algo sea barato no significa que sea rentable .

Si tomamos su argumentación ad absurdum, llegaríamos a la conclusión de que una CPU debería estar compuesta principalmente por cientos de tipos de instrucción NOP, porque son las más baratas de implementar.

O compárelo con instrumentos financieros: ¿compraría un bono de $ 1 con una devolución del 0.01% solo porque puede? No, preferiría ahorrar esos dólares hasta que tenga suficiente para comprar un bono de $ 10 con mejor rendimiento. Lo mismo ocurre con el presupuesto de silicona en una CPU: es efectivo para eliminar muchas operaciones baratas pero inútiles como NAND, y poner los transistores guardados en algo mucho más caro pero realmente útil.

No hay carrera para tener tantas operaciones como sea posible. Como RISC vs CISC había demostrado lo que Turing sabía desde el principio: menos es más. En realidad, es mejor tener la menor cantidad de operaciones posible.

    
respondido por el Agent_L
3

En un nivel de hardware, nand o nor es la operación lógica elemental. Dependiendo de la tecnología (o de lo que llames arbitrariamente 1 y de lo que llamas 0), nand o nor se pueden implementar de una manera muy simple y elemental.

Si ignoramos el caso "ni", toda la otra lógica se construye a partir de nand. Pero no porque exista alguna prueba informática que demuestre que todas las operaciones lógicas se pueden construir a partir de, y la razón es que simplemente no existe ningún método elemental para construir xor, o, etc. luego construyéndolo a partir de nand's.

Para instrucciones de computadora, la situación es diferente. Se podría implementar una instrucción nand, y sería un poco más barato que implementar xor, por ejemplo. Pero solo un poquito, porque la lógica que calcula el resultado es muy pequeña en comparación con la lógica que decodifica la instrucción, mueve los operandos, se asegura de que solo se calcule una operación, recoge el resultado y lo entrega al lugar correcto. Cada instrucción requiere un ciclo para ejecutarse, lo mismo que una adición que es diez veces más complicada en términos de lógica. Los ahorros de nand vs. xor serían despreciables.

Lo que cuenta entonces es cuántas instrucciones se necesitan para las operaciones que en realidad se realizan mediante el código típico . Nand no está en la parte superior de la lista de operaciones comúnmente solicitadas. Es mucho más común que y, o, no se soliciten. Los diseñadores de procesadores y conjuntos de instrucciones examinarán un montón de código existente y determinarán cómo las diferentes instrucciones afectarían ese código. Probablemente encontraron que agregar una instrucción nand llevaría a una reducción muy pequeña en el número de instrucciones del procesador ejecutándose para ejecutar el código típico, y reemplazar alguna instrucción existente con nand aumentaría el número de instrucciones ejecutadas.

    
respondido por el gnasher729
2

Solo porque NAND (o NOR) puede implementar todas las puertas en lógica combinacional, no se traduce a un operador eficiente a nivel de bits de la misma manera. Para implementar un AND utilizando solo las operaciones NAND, donde c = a AND b, tendría que tener c = a NAND b, luego b = -1, luego c = c NAND b (para un NOT). Las operaciones lógicas básicas a nivel de bits son AND, OR, EOR, NOT, NAND y NEOR. Eso no es mucho para cubrir, y los primeros cuatro generalmente están integrados de todos modos. En lógica combinada, los circuitos lógicos básicos solo están limitados por el número de puertas disponibles, que es un juego de pelota completamente diferente. El número de posibles interconexiones en una matriz de puertas programables, que suena como lo que realmente estás buscando, sería un número muy grande. Algunos procesadores sí tienen matrices de puertas integradas.

    
respondido por el Robin Hodson

Lea otras preguntas en las etiquetas