私は何も最適化したくありません、私は誓います、私は好奇心からこの質問をしたいだけです。ほとんどのハードウェアには、単一のコマンドであるビットシフトのアセンブリコマンド(たとえばshl
、shr
)があることを私は知っています。ただし、シフトするビット数は重要ですか(ナノ秒単位、またはCPUタクト単位)。言い換えれば、次のいずれかがどのCPUでも高速ですか?
x << 1;
そして
x << 10;
そして、この質問で私を憎まないでください。:)
私は何も最適化したくありません、私は誓います、私は好奇心からこの質問をしたいだけです。ほとんどのハードウェアには、単一のコマンドであるビットシフトのアセンブリコマンド(たとえばshl
、shr
)があることを私は知っています。ただし、シフトするビット数は重要ですか(ナノ秒単位、またはCPUタクト単位)。言い換えれば、次のいずれかがどのCPUでも高速ですか?
x << 1;
そして
x << 10;
そして、この質問で私を憎まないでください。:)
回答:
CPUに依存する可能性があります。
ただし、最新のCPU(x86、ARM)はすべて、「バレルシフタ」を使用しています。これは、一定時間内に任意のシフトを実行するように特別に設計されたハードウェアモジュールです。
つまり、肝心なのは...いいえ。変わりはない。
60000 mod register_size
ます。たとえば、32ビットプロセッサは、シフトカウントの最下位5ビットのみを使用します。
一部の組み込みプロセッサには、「1つシフト」命令しかありません。こうしたプロセッサでは、コンパイラは変化するであろうx << 3
に((x << 1) << 1) << 1
。
Motorola MC68HCxxは、この制限がある最も人気のあるファミリの1つだったと思います。幸いなことに、このようなアーキテクチャは現在では非常にまれであり、ほとんどの場合、シフトサイズが可変のバレルシフタが含まれています。
多くの最新の派生物を備えたIntel8051も、任意のビット数をシフトすることはできません。
これには多くの場合があります。
多くの高速MPUには、バレルシフタ、マルチプレクサのような電子回路があり、一定時間でシフトを実行します。
MPUのビットシフトx << 10
が1つしかない場合は、通常、10シフトまたは2シフトのバイトコピーで行われるため、速度が遅くなります。
しかし、よりもx << 10
さらに高速になる一般的なケースが知られていますx << 1
。xが16ビットの場合、下位6ビットのみが考慮されるため(他はすべてシフトアウトされます)、MPUは下位バイトのみをロードする必要があるため、8ビットメモリへのアクセスサイクルは1回のみで、x << 10
2回のアクセスサイクルが必要です。アクセスサイクルがシフトより遅い(そして下位バイトをクリアする)場合、x << 10
速くなります。これは、低速の外部データRAMにアクセスしながら、高速のオンボードプログラムROMを備えたマイクロコントローラに適用される場合があります。
ケース3に加えて、コンパイラーはx << 10
、16x16の乗算を16x8の乗算に置き換えるなど(下位バイトは常にゼロであるため)、入力の有効ビット数を考慮し、さらに操作を低幅のビットに最適化する場合があります。
一部のマイクロコントローラには左シフト命令がまったくadd x,x
なく、代わりに使用することに注意してください。
ARMでは、これは別の命令の副作用として実行できます。したがって、潜在的には、どちらにも遅延はまったくありません。
ADD R0, R1, R2 ASL #3
、R1とR2を3ビット左にシフトして追加します。
ここにあります私のお気に入りのCPUれ、x<<2
限り二倍かかるがx<<1
:)
それはCPUとコンパイラの両方に依存します。基盤となるCPUにバレルシフタによる任意のビットシフトがある場合でも、これはコンパイラがそのリソースを利用する場合にのみ発生します。
データのビット単位の幅の外側に何かをシフトすることは、CおよびC ++では「未定義の動作」であることに注意してください。署名されたデータの右シフトも「実装定義」です。速度についてあまり心配するのではなく、異なる実装で同じ答えが得られることを心配してください。
ANSI Cセクション3.3.7からの引用:
3.3.7ビット単位のシフト演算子
構文
shift-expression: additive-expression shift-expression << additive-expression shift-expression >> additive-expression
制約
各オペランドは整数型でなければなりません。
セマンティクス
積分昇格は、各オペランドで実行されます。結果のタイプは、プロモートされた左オペランドのタイプです。右のオペランドの値が負であるか、プロモートされた左のオペランドのビット単位の幅以上の場合、動作は未定義です。
E1 << E2の結果は、E1の左シフトE2ビット位置です。空のビットはゼロで埋められます。E1がunsignedタイプの場合、結果の値はE1に数量を掛けたものです。2はE2の累乗で、E1のタイプがunsigned longの場合はULONG_MAX + 1を法として、それ以外の場合はUINT_MAX +1になります。(定数ULONG_MAXおよびUINT_MAXはヘッダーで定義されています。)
E1 >> E2の結果は、E1が右シフトされたE2ビット位置です。E1に符号なしの型がある場合、またはE1に符号付きの型と非負の値がある場合、結果の値は、E1の商を数量で割った値であり、2の累乗はE2です。E1に符号付きタイプと負の値がある場合、結果の値は実装定義です。
そう:
x = y << z;
"<<":y×2 z(オーバーフローが発生した場合は未定義);
x = y >> z;
">>":実装-符号付きに対して定義されます(ほとんどの場合、算術シフトの結果:y / 2 z)。
1u << 100
UBではないと思います。それはちょうど0です
1u << 100
としてのビットシフトはオーバーフローになる可能性があります。1u << 100
算術シフトは0であるため。ANSICでは<<
、ビットシフトです。en.wikipedia.org/wiki/Arithmetic_shift
x << (y & 31)
コンパイラがターゲットアーキテクチャのシフト命令がカウントをマスクすることを知っている場合(x86のように)、AND命令なしで単一のシフト命令にコンパイルできます。(できれば、マスクをハードコーディングしないでください。マスクを取得するCHAR_BIT * sizeof(x) - 1
などしてください。)これは、入力に関係なくCUBなしで単一の命令にコンパイルされる回転イディオムを作成する場合に役立ちます。(stackoverflow.com/questions/776508/…)。
一部の世代のIntelCPU(P2またはP3?AMDではありませんが、私が正しく覚えていれば)では、ビットシフト操作は途方もなく遅いです。1ビットのビットシフトは、加算を使用できるため、常に高速である必要があります。考慮すべきもう1つの質問は、一定のビット数によるビットシフトが可変長シフトよりも速いかどうかです。オペコードが同じ速度であっても、x86では、ビットシフトの非定数の右側のオペランドがCLレジスタを占有する必要があります。これにより、レジスタ割り当てに追加の制約が課せられ、プログラムの速度も低下する可能性があります。
shlx
/ shrx
/ sarx
(ハスウェル以降、およびRyzenを)。CISCセマンティクス(count = 0の場合はフラグは変更されません)は、ここでx86を傷つけます。 shl r32, cl
Sandybridgeファミリでは3uopsです(ただし、フラグの結果が使用されていない場合、Intelはuopsの1つをキャンセルできると主張しています)。AMDにはシングルuopshl r32, cl
があります(ただし、拡張精度のために低速のダブルシフトshld r32, r32, cl
)
shl r32, cl
と、シフトが終了するまでフロントエンドが停止します。(stackoverflow.com/questions/36510095/…)。コンパイラはこれを認識しておりtest
、シフトのフラグ結果を使用する代わりに、別の命令を使用します。(しかし、これは問題ではないCPUの命令を無駄にします。stackoverflow.com/ questions / 40354978 /…を参照してください)
いつものように、それは周囲のコードコンテキストに依存します:例えばx<<1
、配列インデックスとして使用していますか?またはそれを何か他のものに追加しますか?いずれの場合も、シフト数が少ない(1または2)と、コンパイラーがシフトするだけの場合よりもさらに最適化できることがよくあります。スループット全体とレイテンシーとフロントエンドのボトルネックのトレードオフは言うまでもありません。小さなフラグメントのパフォーマンスは一次元ではありません。
ハードウェアシフト命令は、コンパイラがコンパイルするための唯一のオプションではありませんx<<1
が、他の答えはほとんどそれを前提としています。
x << 1
x+x
unsigned、および2の補数の符号付き整数とまったく同じです。コンパイラーは、コンパイル中にターゲットとするハードウェアを常に認識しているため、このようなトリックを利用できます。
でインテルハスウェル、add
クロックスループットあたり4を持っていますが、shl
当面のカウントでのみ2クロックスループットあたりを持っています。(命令テーブル、およびその他のリンクについては、http://agner.org/optimize/を参照してください。x86タグウィキ)。SIMDベクトルシフトはクロックあたり1(Skylakeでは2)ですが、SIMDベクトル整数加算はクロックあたり2(Skylakeでは3)です。ただし、レイテンシーは同じです:1サイクル。
shl
カウントがオペコードで暗黙的に示される場所の特別なシフトバイワンエンコーディングもあります。8086には即時カウントシフトはなく、1つとcl
レジスターだけでした。これは主に右シフトに関連します。メモリオペランドをシフトしない限り、左シフトに追加できるからです。ただし、後で値が必要になった場合は、最初にレジスタにロードすることをお勧めします。しかし、いずれにせよ、shl eax,1
またはadd eax,eax
1つのバイトよりも短くなってshl eax,10
、そして、コードサイズが直接(デコード/フロントエンドのボトルネック)または間接的(L1Iコードキャッシュミス)がパフォーマンスに影響を与えることができます。
より一般的には、小さなシフトカウントは、x86のアドレッシングモードでスケーリングされたインデックスに最適化できる場合があります。最近一般的に使用されている他のほとんどのアーキテクチャはRISCであり、スケールインデックスアドレッシングモードはありませんが、x86は、これについて言及する価値のある一般的なアーキテクチャです。(たとえば、4バイト要素の配列にインデックスを付ける場合は、スケール係数を1増やす余地がありますint arr[]; arr[x<<1]
)。
の元の値x
がまだ必要な状況では、コピー+シフトが必要になるのが一般的です。ただし、ほとんどのx86整数命令はインプレースで動作します。 (宛先は、add
またはのような命令のソースの1つですshl
。)x86-64 System V呼び出し規約は、レジスターに引数を渡し、最初の引数を入力しedi
、戻り値を入力しますeax
。したがって、戻り値を返す関数x<<10
は、コンパイラーにコピー+シフトを発行させます。コード。
このLEA
命令では、シフトアンドアッドが可能です(アドレッシングモードのマシンエンコーディングを使用しているため、シフトカウントは0から3です)。結果を別のレジスタに入れます。
gccとclangはどちらも、Godboltコンパイラエクスプローラーで確認できるように、これらの関数を同じ方法で最適化します。
int shl1(int x) { return x<<1; }
lea eax, [rdi+rdi] # 1 cycle latency, 1 uop
ret
int shl2(int x) { return x<<2; }
lea eax, [4*rdi] # longer encoding: needs a disp32 of 0 because there's no base register, only scaled-index.
ret
int times5(int x) { return x * 5; }
lea eax, [rdi + 4*rdi]
ret
int shl10(int x) { return x<<10; }
mov eax, edi # 1 uop, 0 or 1 cycle latency
shl eax, 10 # 1 uop, 1 cycle latency
ret
2つのコンポーネントを備えたLEAは、最近のIntelおよびAMDCPUで1サイクルのレイテンシーと2クロックあたり2のスループットを備えています。(Sandybridge-familyおよびBulldozer / Ryzen)。Intelでは、クロックスループットは1つだけで、レイテンシは3cですlea eax, [rdi + rsi + 123]
。(関連:このC ++コードが、コラッツの推測をテストするための手書きのアセンブリよりも高速なのはなぜですか?これについて詳しく説明します。)
とにかく、コピー+シフト10は別のmov
命令が必要です。最近の多くのCPUではレイテンシーがゼロになる可能性がありますが、それでもフロントエンドの帯域幅とコードサイズが必要です。(x86のMOVは本当に「無料」ですか?なぜこれをまったく再現できないのですか?)
また、関連:x86で2つの連続したリール命令のみを使用してレジスタに37を掛ける方法は?。
コンパイラは周囲のコードを自由に変換できるため、実際のシフトが発生したり、他の操作と組み合わされたりすることはありません。
たとえば、if(x<<1) { }
を使用して、and
上位ビットを除くすべてのビットをチェックできます。x86では、の代わりに/のtest
ような命令を使用します。この最適化は、あらゆるシフトカウントで機能し、大量のシフトが遅いマシン(Pentium 4など)または存在しないマシン(一部のマイクロコントローラー)でも機能します。test eax, 0x7fffffff
jz .false
shl eax,1 / jz
多くのISAには、シフトだけでなくビット操作命令があります。たとえば、PowerPCには多くのビットフィールド抽出/挿入命令があります。または、ARMには、他の命令の一部としてソースオペランドのシフトがあります。(したがって、シフト/回転命令はmove
、シフトされたソースを使用する、の特殊な形式にすぎません。)
Cはアセンブリ言語ではないことを忘れないでください。効率的にコンパイルするようにソースコードを調整するときは、常に最適化されたコンパイラ出力を確認してください。