x << 1またはx << 10のどちらが速いですか?


83

私は何も最適化したくありません、私は誓います、私は好奇心からこの質問をしたいだけです。ほとんどのハードウェアには、単一のコマンドであるビットシフトのアセンブリコマンド(たとえばshlshr)があることを私は知っています。ただし、シフトするビット数は重要ですか(ナノ秒単位、またはCPUタクト単位)。言い換えれば、次のいずれかがどのCPUでも高速ですか?

x << 1;

そして

x << 10;

そして、この質問で私を憎まないでください。:)


17
ああ、私はコードをちらっと見て、私の最初の考えは「ストリーム印刷演算子」でした。一休みしたい。
コス

4
誰かが「時期尚早の最適化」とかすかに頭の中で言っているのを聞いていると思います。あるいは私の想像だけかもしれません。
tia 2010年

5
@tia彼は何も最適化するつもりはないと言った:)

1
@Grigoryはい。そのため、ここでそのフレーズの質問をスキップする人は誰もいません。:D
tia 2010年

1
補足として:私は最近、左にシフトすることと右にシフトすることは必ずしも同じCPU時間を消費しないことを認識しました。私の場合、右へのシフトははるかに遅かった。最初は驚きましたが、答えは、左にシフトすることは論理的であり、右にシフトすることは算術的であることを意味すると思います:stackoverflow.com/questions/141525/…–
Christian Ammer

回答:


84

CPUに依存する可能性があります。

ただし、最新のCPU(x86、ARM)はすべて、「バレルシフタ」を使用しています。これは、一定時間内に任意のシフトを実行するように特別に設計されたハードウェアモジュールです。

つまり、肝心なのは...いいえ。変わりはない。


21
グレートは、今、私はバレルロールを行うには、私のCPUを伝えるのイメージを持っていることは...私の頭の中で立ち往生
イグナシオバスケス-エイブラムス

11
Errr-非常に多くはプロセッサに依存します。一部のプロセッサでは、これは一定時間です。その他の場合は、シフトごとに1サイクルになる可能性があります(プロセッサのクロック速度をs / wで測定する方法として、シフトを約60,000桁使用したことがあります)。また、他のプロセッサでは、シングルビットシフトの命令しかない場合があります。その場合、マルチビットシフトは、反復するループ内にあるライブラリルーチンに委任されます。
quick_now 2010年

4
@quickly_now:それは確かにクロック速度を測定する悪い方法です。実際に60,000シフトを実行するほど愚かなプロセッサはありません。それは単にに変換され60000 mod register_sizeます。たとえば、32ビットプロセッサは、シフトカウントの最下位5ビットのみを使用します。
casablanca 2010年

4
inmosトランスピュータには、32ビットのオペランドであるシフト数を取得するシフト演算子がありました。必要に応じて、それぞれ1クロックで40億シフトを実行できます。「十分に愚かなプロセッサはありません」。申し訳ありませんが、間違いました。これはしました。ただし、その部分をアセンブラでコーディングする必要があります。コンパイラーは賢明な変更/最適化を行いました(結果を0に設定するだけで、何もしません)。
quick_now 2010年

5
悲しいことに、Pentium 4はバレルシフタを失いました。これは、全体的なクロックあたりの命令数の低下の一因となりました。CoreBlahアーキテクチャがそれを取り戻したと思います。
Russell Borogove 2010年

64

一部の組み込みプロセッサには、「1つシフト」命令しかありません。こうしたプロセッサでは、コンパイラは変化するであろうx << 3((x << 1) << 1) << 1

Motorola MC68HCxxは、この制限がある最も人気のあるファミリの1つだったと思います。幸いなことに、このようなアーキテクチャは現在では非常にまれであり、ほとんどの場合、シフトサイズが可変のバレルシフタが含まれています。

多くの最新の派生物を備えたIntel8051も、任意のビット数をシフトすることはできません。


12
組み込みマイクロコントローラではまだ一般的です。
ベンジャクソン

4
「レア」とはどういう意味ですか?したがって、統計によれば、販売された8ビットマイクロコントローラーの数は、他のすべてのタイプのMPUの数よりも多くなっています。
ボバニウム2010年

より多くのプログラムROM、より多くの動作RAM、およびより多くの機能を備えたユニットあたり同じ価格で16ビットを入手できる場合(TIのMSP430など)、8ビットマイクロコントローラーは新しい開発にはあまり使用されていません。また、一部の8ビットマイクロコントローラーにもバレルシフタが搭載されています。
Ben Voigt

1
マイクロコントローラのワードサイズは、バレルシフタがあるかどうかとは関係ありません。前述のMC68HCxxファミリには16ビットプロセッサもあり、すべてが一度に1ビット位置のみをシフトします。
Ben Voigt

ほとんどの8ビットMCUにはバレルシフタがないという事実がありますが、それは真実ではなく、バレルシフタのない8ビット以外のものもあります。ビットネスは、バレルシフタを備えていないマシンの信頼できる近似値として得られました。また、MCUのCPUコアはモデルの選択を設定しないことがよくありますが、オンチップ周辺機器は選択​​を設定します。また、同じ価格でより豊富な周辺機器には8ビットが選択されることがよくあります。
ボバニウム2010年

29

これには多くの場合があります。

  1. 多くの高速MPUには、バレルシフタ、マルチプレクサのような電子回路があり、一定時間でシフトを実行します。

  2. MPUのビットシフトx << 10が1つしかない場合は、通常、10シフトまたは2シフトのバイトコピーで行われるため、速度が遅くなります。

  3. しかし、よりx << 10さらに高速になる一般的なケースが知られていますx << 1。xが16ビットの場合、下位6ビットのみが考慮されるため(他はすべてシフトアウトされます)、MPUは下位バイトのみをロードする必要があるため、8ビットメモリへのアクセスサイクルは1回のみで、x << 102回のアクセスサイクルが必要です。アクセスサイクルがシフトより遅い(そして下位バイトをクリアする)場合、x << 10速くなります。これは、低速の外部データRAMにアクセスしながら、高速のオンボードプログラムROMを備えたマイクロコントローラに適用される場合があります。

  4. ケース3に加えて、コンパイラーはx << 10、16x16の乗算を16x8の乗算に置き換えるなど(下位バイトは常にゼロであるため)、入力の有効ビット数を考慮し、さらに操作を低幅のビットに最適化する場合があります。

一部のマイクロコントローラには左シフト命令がまったくadd x,xなく、代わりに使用することに注意してください。


わかりません。なぜx << 10がx << 8よりも速いのですか。x<< 8では、16ビットの下位バイトからロードを実行する必要があります。ロードと2シフトは実行しません。わかりません。
なし

3
@none:x << 10がx << 8よりも速いとは言いませんでした。
ボバニウム2010年

9

ARMでは、これは別の命令の副作用として実行できます。したがって、潜在的には、どちらにも遅延はまったくありません。


1
命令は同じサイクル数で実行されますか?いくつかのアーキテクチャでは、同じ命令がオペランドに基づいていくつかの異なるオペコードに変換され、1〜5サイクルかかります。
ニックT

@Nick ARM命令は通常、1〜2サイクルかかります。新しいアーキテクチャではわかりません。
onemasse 2010年

2
@Nick T:彼はARMについて話していますが、これは専用の命令としてではなく、多くのデータ処理命令の「機能」としてシフトしています。つまりADD R0, R1, R2 ASL #3、R1とR2を3ビット左にシフトして追加します。
ボバニウム2010年


7

それは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 << 100UBではないと思います。それはちょうど0です
アーメンTsirunyan

@Armen Tsirunyan:ビットシフト1u << 100としてのビットシフトオーバーフローになる可能性があります。1u << 100算術シフトは0であるため。ANSICでは<<、ビットシフトです。en.wikipedia.org/wiki/Arithmetic_shift
オオカミ

2
@Armen Tsirunyan:ANSIセクション3.3.7を参照してください-右のオペランドの値が負であるか、プロモートされた左のオペランドのビット幅以上の場合、動作は未定義です。したがって、101以上のビットタイプがない限り、あなたの例は任意のANSICシステム上のUBです。
オオカミ

@ carrot-pot:OK、あなたは私を納得させました:)
Armen Tsirunyan 2010年

関連:x << (y & 31)コンパイラがターゲットアーキテクチャのシフト命令がカウントをマスクすることを知っている場合(x86のように)、AND命令なしで単一のシフト命令にコンパイルできます。(できれば、マスクをハードコーディングしないでください。マスクを取得するCHAR_BIT * sizeof(x) - 1などしてください。)これは、入力に関係なくCUBなしで単一の命令にコンパイルされる回転イディオムを作成する場合に役立ちます。(stackoverflow.com/questions/776508/…)。
ピーターコーデス2017

7

8ビットプロセッサでx<<1は、実際には16ビット値よりはるかに低速になる可能性があると考えられx<<10ます。

たとえば、の合理的な翻訳は次のx<<1ようになります。

byte1 = (byte1 << 1) | (byte2 >> 7)
byte2 = (byte2 << 1)

一方、x<<10より単純になります。

byte1 = (byte2 << 2)
byte2 = 0

x<<1より頻繁に、さらには遠くにシフトすることに注意してくださいx<<10。さらに、の結果x<<10はbyte1の内容に依存しません。これにより、操作がさらに高速化される可能性があります。


5

一部の世代のIntelCPU(P2またはP3?AMDではありませんが、私が正しく覚えていれば)では、ビットシフト操作は途方もなく遅いです。1ビットのビットシフトは、加算を使用できるため、常に高速である必要があります。考慮すべきもう1つの質問は、一定のビット数によるビットシフトが可変長シフトよりも速いかどうかです。オペコードが同じ速度であっても、x86では、ビットシフトの非定数の右側のオペランドがCLレジスタを占有する必要があります。これにより、レジスタ割り当てに追加の制約が課せられ、プログラムの速度も低下する可能性があります。


1
それがPentium4です。PProから派生したCPU(P2やP3など)のシフトは高速です。そして、はい、x86での可変カウントシフトはあなたがBMI2使用することができない限り、彼らは、可能性よりも遅いですshlx/ shrx/ sarx(ハスウェル以降、およびRyzenを)。CISCセマンティクス(count = 0の場合はフラグは変更されません)は、ここでx86を傷つけます。 shl r32, clSandybridgeファミリでは3uopsです(ただし、フラグの結果が使用されていない場合、Intelはuopsの1つをキャンセルできると主張しています)。AMDにはシングルuopshl r32, clがあります(ただし、拡張精度のために低速のダブルシフトshld r32, r32, cl
Peter Cordes 2017

1
シフト(可変カウントでも)はP6ファミリの単一のuopにすぎませんが、フラグの結果を読み取るshl r32, clと、シフトが終了するまでフロントエンドが停止します。(stackoverflow.com/questions/36510095/…)。コンパイラはこれを認識しておりtest、シフトのフラグ結果を使用する代わりに、別の命令を使用します。(しかし、これは問題ではないCPUの命令を無駄にします。stackoverflow.com/ questions / 40354978 /…を参照してください)
Peter Cordes 2017

3

いつものように、それは周囲のコードコンテキストに依存します:例えばx<<1、配列インデックスとして使用していますか?またはそれを何か他のものに追加しますか?いずれの場合も、シフト数が少ない(1または2)と、コンパイラーがシフトするだけの場合よりもさらに最適化できることがよくあります。スループット全体とレイテンシーとフロントエンドのボトルネックのトレードオフは言うまでもありません。小さなフラグメントのパフォーマンスは一次元ではありません。

ハードウェアシフト命令は、コンパイラがコンパイルするための唯一のオプションではありませんx<<1が、他の答えはほとんどそれを前提としています。


x << 1x+xunsigned、および2の補数の符号付き整数とまったく同じです。コンパイラーは、コンパイル中にターゲットとするハードウェアを常に認識しているため、このようなトリックを利用できます。

インテルハスウェルaddクロックスループットあたり4を持っていますが、shl当面のカウントでのみ2クロックスループットあたりを持っています。(命令テーブル、およびその他のリンクについては、http://agner.org/optimize/を参照してください。タグウィキ)。SIMDベクトルシフトはクロックあたり1(Skylakeでは2)ですが、SIMDベクトル整数加算はクロックあたり2(Skylakeでは3)です。ただし、レイテンシーは同じです:1サイクル。

shlカウントがオペコードで暗黙的に示される場所の特別なシフトバイワンエンコーディングもあります。8086には即時カウントシフトはなく、1つとclレジスターだけでした。これは主に右シフトに関連します。メモリオペランドをシフトしない限り、左シフトに追加できるからです。ただし、後で値が必要になった場合は、最初にレジスタにロードすることをお勧めします。しかし、いずれにせよ、shl eax,1またはadd eax,eax1つのバイトよりも短くなって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, 0x7fffffffjz .falseshl eax,1 / jz

多くのISAには、シフトだけでなくビット操作命令があります。たとえば、PowerPCには多くのビットフィールド抽出/挿入命令があります。または、ARMには、他の命令の一部としてソースオペランドのシフトがあります。(したがって、シフト/回転命令はmove、シフトされたソースを使用する、の特殊な形式にすぎません。)

Cはアセンブリ言語ではないことを忘れないでください。効率的にコンパイルするようにソースコードを調整するときは、常に最適化されたコンパイラ出力を確認してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.