このような操作を 1 つの命令で実行できるアーキテクチャは多数あります。たとえば、a*2 + b
コンパイルすると
lea eax, [rsi+rdi*2]
x86-64 で
add r0, r1, r0, lsl #1
アーム上
add w0, w1, w0, lsl 1
ARM64 で
lda16 r0, r1[r0]
xcoreで
コンパイラは式を適切に最適化します。a *= 2; a += b
多くの場合、可読性を低下させるようなことをする理由はありません
Compiler Explorerでデモを見ることができます。
ただし、この操作を何十億回も行うという理由だけでそれを尋ねると、これは本質的にXY 問題になります。なぜなら、C バージョンを変更することは正しい方法ではなく、命令の数を減らすことはランタイムを減らす方法ではないからです。命令数でパフォーマンスを測定しない
最新の CPU はスーパースカラーであり、一部の命令はマイクロコード化されているため、単一の複雑な命令は、並列で実行できる複数の単純な命令よりも遅くなる場合があります。コンパイラは明らかにこれを認識しており、コンパイル中にレイテンシを考慮に入れます。本当の解決策は、マルチスレッドと SIMD を使用することです
たとえば、Clang は AVX-512 のメイン ループで次の命令を発行します。
vpaddd zmm0, zmm0, zmm0 ; a *= 2
vpaddd zmm1, zmm1, zmm1
vpaddd zmm2, zmm2, zmm2
vpaddd zmm3, zmm3, zmm3
vpaddd zmm0, zmm0, zmmword ptr [rsi + 4*rdx] ; a += b
vpaddd zmm1, zmm1, zmmword ptr [rsi + 4*rdx + 64]
vpaddd zmm2, zmm2, zmmword ptr [rsi + 4*rdx + 128]
vpaddd zmm3, zmm3, zmmword ptr [rsi + 4*rdx + 192]
これには、ループ展開と自動ベクトル化の両方が含まれます。各命令は、一度に16 個の 32 ビット整数を処理できます。もちろん、64 ビットを使用している場合int
は、一度に「のみ」8 で動作します。さらに、同じ命令はそれぞれ独立して実行できるため、CPU に十分な実行ポートがあれば、64 個int
の命令を並列に追加できます。これが「高速」と呼ばれるものです
GCC は、多くの場合、ループのアンローリングであまり積極的ではなく、vpslld
その後に. を使用しvpaddd
ます。しかし、それでもスカラー バージョンよりも高速です。ARM では、neon が使用されていることがわかりますshl v0.4s, v0.4s, 1; add v0.4s, v0.4s, v1.4s
。コンパイラ エクスプローラのデモ リンクは次のとおりです。
「最適化」よりもはるかに高速なマルチスレッドと組み合わせる