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.