Eu gostaria de concordar com Brian aqui, Wouter e pjc50.
Eu também gostaria de acrescentar que, de propósito geral, especialmente os processadores CISC, as instruções nem todas têm as mesmas taxas de transferência - uma operação complicada pode simplesmente levar mais ciclos do que a fácil.
Considere o X86: AND
(que é uma operação "e") é provavelmente muito rápido. O mesmo vale para NOT
. Vejamos um pouco de desmontagem:
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 produzir montagem:
gcc -O3 -c -S -mavx512f test.c
Conjunto de saída (reduzido):
.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 você pode ver, para os tipos de dados com tamanho abaixo de 64, tudo é tratado como comprido (daí eu e não l ), já que essa é a largura de bits "nativa" do meu compilador, ao que parece.
O fato de existirem mov
s entre isso se deve apenas ao fato de eax
ser o registrador que contém o valor de retorno de uma função. Normalmente, você apenas calcula o edi
registro de uso geral para calcular o resultado.
Para 64 bits, é o mesmo - apenas com as q
palavras "quad" (portanto, à direita ) e rax
/ em rsi
vez de eax
/ edi
.
Parece que, para operandos de 128 bits e maiores, a Intel não se importou em implementar uma operação "não"; em vez disso, o compilador produz um 1
registro completo (auto-comparação do registro consigo mesmo, resultado armazenado no registro com a vdcmpeqd
instrução) e xor
é isso.
Resumindo: ao implementar uma operação complicada com várias instruções elementares, você não necessariamente desacelera a operação - simplesmente não há vantagem em ter uma instrução que executa várias tarefas se não for mais rápida.