ここでブライアンと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サイズのデータ型の場合、物事はすべてlongとして処理されます(したがって、lではなくand lです)。
そのそこの事実mov
の間では唯一の事実によるものであるeax
関数の戻り値を含むレジスタです。通常、あなただけで計算しますedi
汎用レジスターで計算して、結果を計算します。
64ビットの場合も同じです。 "quad"(したがって、末尾のq
)単語、およびrax
/のrsi
代わりにeax
/を使用するだけedi
です。
128ビット以上のオペランドの場合、Intelは「not」操作の実装を気にしなかったようです。代わりに、コンパイラーは全1
レジスター(レジスターとそれ自体の自己比較、結果はvdcmpeqd
命令とレジスターに保管されます)xor
を生成します。
要するに、複数の基本命令で複雑な操作を実装することで、必ずしも操作が遅くなるわけではありません。複数の命令を処理する1つの命令が高速でない場合、それだけの利点はありません。