C++の_rotl
および_rotr
のC#相当(.NET 2.0)は何ですか?
これはあなたがやろうとしていることですか?
基本的にあなたが欲しいのは
(左用)
(original << bits) | (original >> (32 - bits))
または
(右)
(original >> bits) | (original << (32 - bits))
また、Mehrdadが既に示唆しているように、これはuintでのみ機能します。これは、Jonが提供する例でもあります。
C#のビットローテーション用の組み込み言語機能はありませんが、これらの拡張メソッドが機能します。
public static uint RotateLeft(this uint value, int count)
{
return (value << count) | (value >> (32 - count))
}
public static uint RotateRight(this uint value, int count)
{
return (value >> count) | (value << (32 - count))
}
注: Mehrdadが指摘するように、右シフト(>>
)は、符号付き整数の場合に特有です。符号なし整数の場合のように、MSBに0ではなく符号ビットを入力します。代わりにuint
(符号なし32ビット整数)を取得して返すようにメソッドを変更しました。これもC++のrotl
およびrotr
関数に準拠しています。整数をローテーションする場合は、渡す前に大文字と小文字を区別し、もちろん戻り値をキャストします。
使用例:
int foo1 = 8.RotateRight(3); // foo1 = 1
int foo2 = int.MinValue.RotateLeft(3); // foo2 = 4
(ご了承ください int.MinValue
は111111111111111111111111-32の2進数で1です。)
最新のC#7を使用して、by-ref拡張メソッド。ヘルパー関数からの戻り値を変数に絶えず格納するという煩雑な作業を取り除くことができます。
これにより、回転関数が見事に合理化され、関数の戻り値を再格納するのを忘れている一般的なバグのクラスがなくなりますが、ValueTypes
が誤って取得する新しい完全に異なるタイプのバグが発生する可能性があります変更したいin-situしたくない、または期待しない場合。
_public static void Rol(ref this ulong ul) => ul = (ul << 1) | (ul >> 63);
public static void Rol(ref this ulong ul, int N) => ul = (ul << N) | (ul >> (64 - N));
public static void Ror(ref this ulong ul) => ul = (ul << 63) | (ul >> 1);
public static void Ror(ref this ulong ul, int N) => ul = (ul << (64 - N)) | (ul >> N);
/// note: ---^ ^---^--- extension method can now use 'ref' for ByRef semantics
_
通常、私はこれらのような小さなメソッドに[MethodImpl(MethodImplOptions.AggressiveInlining)]
を確実に配置しますが、(x64で)調査した結果、ここではまったく必要がないことがわかりました。メソッドが適格であるとJITが判断した場合(たとえば、デフォルトで有効になっているVisualStudioデバッガーのチェックボックス 'Suppress JIT Optimization' をオフにした場合)、メソッドは関係なくインライン化されます。 。
用語がよくわからない場合は、[〜#〜] jit [〜#〜]、または「ジャストイン-時間」は、実行時に検出されたプラットフォーム用に調整されたネイティブコードへのIL命令の1回限りの変換を指します。このプロセスは、オンデマンドで、メソッドごとに。NETプログラムが実行されます。
by-ref拡張メソッドの使用を示すために、最初の上記の「左回転」の方法で、従来のby-value拡張機能のJIT出力を比較しますメソッドと新しいby-refアプローチ。 x64Releaseで比較される2つのテスト方法は、。NET 4.7Windows 10で。上記のように、これはJIT最適化で「抑制されない」ため、これらのテスト条件下では、ご覧のとおり、関数は完全に消えますインラインコードに。
_static ulong Rol_ByVal(this ulong ul) => (ul << 1) | (ul >> 63);
static void Rol_ByRef(ref this ulong ul) => ul = (ul << 1) | (ul >> 63);
// notice reassignment here ---^ (c̲a̲l̲l̲e̲e̲ doing it instead of caller)
_
そして、対応する各呼び出しサイトのC#コードを次に示します。完全にJIT最適化されたAMD64コードは非常に小さいため、ここに含めることもできます。これが最適なケースです。
_static ulong x = 1; // static so it won't be optimized away in this simple test
// ------- ByVal extension method; c̲a̲l̲l̲e̲r̲ must reassign 'x' with the result -------
x = x.Rol_ByVal();
// 00007FF969CC0481 mov rax,qword ptr [7FF969BA4888h]
// 00007FF969CC0487 rol rax,1
// 00007FF969CC048A mov qword ptr [7FF969BA4888h],rax
// ------- New in C#7, ByRef extension method can directly alter 'x' in-situ -------
x.Rol_ByRef();
// 00007FF969CC0491 rol qword ptr [7FF969BA4888h],1
_
ワオ。はい、それは冗談ではありません。すぐに、ECMA CIL(.NET)中間言語の_OpCodes.Rot
_-ファミリーの命令が明らかに不足していることがわかりますほとんど問題ではありません。ジッターは、C#回避策コード_(ul << 1) | (ul >> 63)
_の山を通して、その本質的な目的を理解することができました。どちらの場合も、x64 JITは、ネイティブrol
命令を発行するだけで実装します。印象的なことに、ByRefバージョンは単一の命令を使用して、レジスタにロードすることなく、メインメモリのターゲットアドレスで直接ローテーションを実行します。
ByValの場合でも、呼び出されたメソッドが完全に最適化される前に(値タイプのセマンティクスの本質であるように)、呼び出し元の元の値を変更せずにおくために必要であった過剰コピーの残りの痕跡が依然として見られます。ここで整数回転の場合、それは64ビットレジスタrax
へのターゲット整数の追加のフェッチ/ストアです。
それを明確にするために、デバッグセッションでJIT最適化を再度抑制しましょう。そうすることで、ヘルパー拡張メソッドが復活し、前の段落の最初の文をよりよく説明するための完全な本文とスタックフレームが表示されます。まず、通話サイトを見てみましょう。ここでは、従来のValueType
セマンティクスの効果を確認できます。つまり、下位のスタックフレームが親フレームのValueType
コピーを操作できないことを保証することになります。
by-value:
_ x = x.Rol_ByVal();
// 00007FF969CE049C mov rcx,qword ptr [7FF969BC4888h]
// 00007FF969CE04A3 call 00007FF969CE00A8
// 00007FF969CE04A8 mov qword ptr [rbp-8],rax
// 00007FF969CE04AC mov rcx,qword ptr [rbp-8]
// 00007FF969CE04B0 mov qword ptr [7FF969BC4888h],rcx
_
by-reference
_ x.Rol_ByRef();
// 00007FF969CE04B7 mov rcx,7FF969BC4888h
// 00007FF969CE04C1 call 00007FF969CE00B0
// ...all done, nothing to do here; the callee did everything in-place for us
_
これら2つのフラグメントのそれぞれに関連付けられたC#コードから予想されるように、by-val呼び出し元は、呼び出しが戻った後に行うべき一連の作業を行います。これは、ulong
レジスタに返される完全に独立したulong
値でrax
値「x」の親コピーを上書きするプロセスです。
次に、呼び出されたターゲット関数のコードを見てみましょう。それらを見るには、JITに最適化を「抑制」させる必要があります。以下は、x(64)JITが_Rol_ByVal
_および_Rol_ByRef
_関数に対して発行するネイティブコードです。
2つの小さな重要な違いに焦点を合わせるために、管理上の定型文の一部を取り除きました。 (私はコンテキストのスタックフレームのセットアップとティアダウンを残しました。この例で、その補助的なものが実際のコンテンツの指示をかなり小さくする方法を示すために)。ByRefの間接参照が機能していることを確認できますか?まあ、それは私がそれを指摘したのに役立ちます:-/
_ static ulong Rol_ByVal(this ulong ul) => (ul << 1) | (ul >> 63);
// 00007FF969CD0760 Push rbp
// 00007FF969CD0761 sub rsp,20h
// 00007FF969CD0765 lea rbp,[rsp+20h]
// ...
// 00007FF969CE0E4C mov rax,qword ptr [rbp+10h]
// 00007FF969CE0E50 rol rax,1
// 00007FF969CD0798 lea rsp,[rbp]
// 00007FF969CD079C pop rbp
// 00007FF969CD079D ret
static void Rol_ByRef(ref this ulong ul) => ul = (ul << 1) | (ul >> 63);
// 00007FF969CD0760 Push rbp
// 00007FF969CD0761 sub rsp,20h
// 00007FF969CD0765 lea rbp,[rsp+20h]
// ...
// 00007FF969CE0E8C mov rax,qword ptr [rbp+10h]
// 00007FF969CE0E90 rol qword ptr [rax],1 <--- !
// 00007FF969CD0798 lea rsp,[rbp]
// 00007FF969CD079C pop rbp
// 00007FF969CD079D ret
_
両方の呼び出しが実際に参照によってulong
値の親のインスタンスを渡していることに気付くかもしれません-両方の例はこの点で同じです。親は、ul
のプライベートコピーが上位スタックフレームに存在するアドレスを示します。これらのポインターに書き込みを行わないことが確実である限り、それらが存在するインスタンスをreadingから呼び出し先を隔離する必要がないことがわかります。これは、「遅延」または遅延アプローチで、各下位(子)スタックフレームに責任を割り当て、ValueTypeセマンティクスを保持します上位の呼び出し元。子が最終的に上書きしない場合、呼び出し側が子フレームに渡されたValueType
をプロアクティブにコピーする必要はありません。不必要なコピーをできるだけ回避するために、可能な限り最新の判断を下せるのは子供だけです。
また、興味深いのは、ここで示した最初の「ByVal」の例でのrax
の不格好な使用についての説明があるかもしれません。値による方法がインライン化によって完全に削減された後、なぜローテーションがまだレジスターで発生する必要があるのですか?
これらの最新の2つのフルメソッドボディバージョンでは、最初のメソッドがulong
を返し、2番目のメソッドがvoid
であることは明らかです。戻り値はrax
で渡されるので、ここのByValメソッドはとにかくそれをそのレジスターにフェッチする必要があるので、そこでも回転させるのは簡単です。 ByRefメソッドは値を返す必要がないため、rax
はもちろんのこと、呼び出し元に何も貼り付ける必要はありません。 「rax
に煩わされる必要がない」というのは、ByRefコードを解放して、親が共有しているulong
インスタンスをターゲットとする「どこにあるか」というファンシーな_qword ptr
_レジスタを使用する代わりに、親のスタックフレームメモリに間接的に。それが、私が先に見た「残余のrax
」の謎についての私の推測ですが、おそらく信頼できる説明です。
単純なシフトのバージョンは機能しません。その理由は、右シフトの符号付き数値は、左のビットを符号ビット、0ではないで埋めます:
この事実は次のようにして確認できます。
Console.WriteLine(-1 >> 1);
正しい方法は次のとおりです。
public static int RotateLeft(this int value, int count)
{
uint val = (uint)value;
return (int)((val << count) | (val >> (32 - count)));
}
public static int RotateRight(this int value, int count)
{
uint val = (uint)value;
return (int)((value >> count) | (value << (32 - count)));
}
短い整数値を操作するオーバーロードを作成する場合は、次に示すように、ステップを追加する必要があることに注意してください。
public static byte RotateLeft(
this byte value,
int count )
{
// Unlike the RotateLeft( uint, int ) and RotateLeft( ulong, int )
// overloads, we need to mask out the required bits of count
// manually, as the shift operaters will promote a byte to uint,
// and will not mask out the correct number of count bits.
count &= 0x07;
return (byte)((value << count) | (value >> (8 - count)));
}
32ビットおよび64ビットのオーバーロードでは、マスキング操作は必要ありません。シフトオペレーター自体が左側のオペランドのそれらのサイズに対応するからです。