シフト操作はO(1)
またはO(n)
ですか?
コンピュータが一般的に1桁シフトするのではなく、31桁シフトするためにより多くの操作を必要とすることは理にかなっていますか?
または、シフトする必要のある場所の数に関係なく、シフトに必要な操作の数が一定であることは理にかなっていますか?
PS:ハードウェアが適切なタグかどうか疑問に思います。
一部の命令セットは、命令ごとに1ビットシフトに制限されています。また、一部の命令セットでは、1つの命令でシフトするビット数を指定できます。これは、最近のプロセッサでは通常1クロックサイクルかかります(現代は意図的にあいまいなワードです)。バレルシフタ、1回の操作で複数のビットをシフトする回路については dan04の回答 を参照してください。
それはすべて論理アルゴリズムに要約されます。結果の各ビットは、入力に基づく論理関数です。単一の右シフトの場合、アルゴリズムは次のようになります。
しかし、論理方程式は同じように簡単に次のようになります。
論理ゲートは非同期であるため、これらすべてを1クロックサイクルで実行できます。それでも、比較しているのが命令のこれら2つのフレーバーだけである場合、シングルシフトによりクロックサイクルが速くなり、ゲートが少なくなることは事実です。または、解決に時間がかかるようにすることもできます。そのため、命令には2〜3クロックなどがかかり、ロジックは3にカウントされ、結果をラッチします。
たとえば、MSP430にはシングルビットの右回転命令しかありません(シングルビットシフトまたは別の命令で左回転を実行できるため、読者に説明を任せます)。
ARM命令セットでは、即時およびレジスタベースのマルチビット回転、算術シフト、論理シフトの両方が可能です。実際の回転命令は1つだけで、もう1つはエイリアスです。右回転32と同じですが、マルチビット回転を実装するために必要なのは一方向バレルシフタだけです。
X86のSHLは、命令ごとに複数のビットを許可しますが、以前は複数のクロックを使用していました。
など、そこにある命令セットを簡単に調べることができます。
あなたの質問に対する答えは、それが修正されていないということです。 1つの操作、1つのサイクル、1つの命令である場合もあります。 1つの命令が複数のクロックサイクルである場合もあります。時にはそれは複数の命令、複数のクロックサイクルです。
多くの場合、コンパイラーはこれらの種類のものを最適化します。スワップバイト命令を含む16ビットレジスタ命令セットと、即時であるが1ビットシフトのみのAND命令があるとします。 8ビットのシフトには8シフト命令サイクルが必要だと思うかもしれませんが、バイト(1命令)をスワップしてから下半分をゼロにANDすることができます(2命令が必要な場合もあれば、2ワードの可変ワード長命令の場合もあります。または、単一の命令にエンコードする場合もあります)、8ではなく2または3命令/クロックサイクルしかかかりません。9ビットのシフトの場合、同じことを実行してシフトを追加し、3または4に対して9クロックにすることができます。また、一部のアーキテクチャでは、8などでシフトするよりも256で乗算する方が高速です。各命令セットには独自の制限とトリックがあります。
ほとんどの命令セットがマルチビットを提供することも、ほとんどがシングルビットに制限することもありません。 X86、ARM、PowerPC、MIPSなどの「コンピューター」カテゴリーに分類されるプロセッサーは、1つの操作に傾倒してシフトします。すべてのプロセッサに拡張しますが、必ずしも今日一般的に使用されている「コンピュータ」とは限りません。逆にシフトすると、マルチビットよりもシングルビットの方が多いため、マルチビットシフトを実行するには複数の操作が必要です。
バレルシフタ は、シフトをO(log n)
パスで実行できるようにします。これは同じクロックサイクルで実行でき、シフトをO(1)
操作にします。
すでに述べたように、バレルシフタはオペランドを一定時間で任意の距離にシフトできます。ただし、バレルシフタはCPUダイ上でかなりのスペースを消費するため、すべてのCPU設計に含まれているわけではありません。
かなりよく知られている例の1つとして、Intel Pentium IIIにはバレルシフタが含まれていましたが、Pentium IVには含まれていましたnot。バレルシフタが存在することを前提としてPentiumIII用に記述されたコードは、PentiumIVでかなり遅くなることがありました。 1.2 GHz Pentium IIIでは、2.8 GHz Pentium IVよりも約4倍高速に実行される暗号化コード(多くのシフトと回転を含む)がありました。
ビットシフトは、実質的にすべての現在のプロセッサでO(1)です。
たとえば、x86の「shrw」命令を見てください。 (AT&T構文の)最初のオペランドは、シフトするビット数です。コンパイラがシフトを実装する方法はコンパイラによって異なりますが、プロセッサが一度にNビットをシフトできる場合にシフトをループに入れるのはばかげています。
補遺:Re:「左に31シフトするには、さらに多くの操作が必要ですか?」シフトにはさまざまな種類がありますが(理由がわからない場合は、レジスタからシフトオフされたビットをどうするかを検討してください)、ほとんどのプロセッサは、GPRが格納できるビット数の単一命令シフトを実行できます。 32ビットレジスタで40ビットシフトを実行するには、複数のレジスタ間でシフトする必要があり(これは、64ビット番号が2つの32ビットレジスタ間で格納されることを前提としています)、私が知っているすべてのプロセッサで、より多くの命令が必要になります。それでもO(1)であり、おそらく1クロックではありません。興味深い補足として、PentiumIVプロセッサはビットシフトで驚くほど遅いです。 Intelはこれまで、ビットシフトによる^ 2の除算と乗算の最適化を推奨してきたため、これは皮肉なことです。詳細については、 このPDF およびGoogleを参照してください。
通常のハードウェアの場合、固定サイズのレジスタは、シフトする場所の数に関係なく一定です。
また、ここではO表記の使用法が非常に奇妙であることに注意してください。通常は、シフトする場所の数ではなく、シフトする数に基づいてアルゴリズムの複雑さを示すために使用します。
Ahem、c#の好奇心からそれをテストし、面白い結果を得ました。
var sw = Stopwatch.StartNew();
long l = 1;
for (long i = 0; i < 20000000; i++) {
l = l << 60; l = l >> 60;
l = l << 60; l = l >> 60;
l = l << 60; l = l >> 60;
//...
// 50 of ^them^ total
}
Console.WriteLine(l + " " + sw.Elapsed);
私のPCでは1.2秒かかります。しかし、私が交換した場合
l = l << 60; l = l >> 60;
と
l = l << 1; l = l >> 1;
その後、時間増加から2.0秒になります。ここでどのような最適化が行われているのかわかりませんが、奇妙に見えます。
具体的な例として、表C-17。汎用命令のIntel®64およびIA-32アーキテクチャ最適化リファレンスマニュアル:
SAL/SAR/SHL/SHR reg, imm 1 cycle latency
SAL/SAR/SHL/SHR reg, cl 1.5 cycles latency
したがって、これは依然として一定の係数であり、O(1.5) = O(1)です。外れ値としてより単純なマイクロアーキテクチャが存在する可能性がありますが、一般的にはO(1)です。