ASM でのリトルエンディアンからビッグエンディアンへの高速変換
-
20-09-2019 - |
質問
C# に uint 型の配列があります。プログラムがリトル エンディアン マシンで動作するかどうかを確認した後、データをビッグ エンディアン型に変換したいと考えています。データ量は非常に大きくなる可能性がありますが、常に均等であるため、パフォーマンスを向上させるために 2 つの uint 型を ulong 型として考慮し、ASM でプログラムすることを考えていました。そのため、非常に高速 (可能な限り最速) を探しています。 ) リトルエンディアンをビッグエンディアンに変換するアセンブラー アルゴリズム。
解決
大量のデータの場合、 bswap
命令 (Visual C++ では _byteswap_ushort
, _byteswap_ulong
, 、 そして _byteswap_uint64
組み込み)が最適な方法です。これは、手書きのアセンブリよりも優れたパフォーマンスを発揮します。これらは、P/Invoke を使用しない純粋な C# では使用できないため、次のようになります。
- 持っている場合にのみこれを使用してください たくさん バイトスワップするデータの数。
- データをマネージド配列に取り込む前にスワップできるように、最低レベルのアプリケーション I/O をマネージド C++ で記述することを真剣に検討する必要があります。すでに C++ ライブラリを作成する必要があるため、失うものはあまりなく、大規模なデータセットで動作する複雑さの低いアルゴリズムの P/Invoke 関連のパフォーマンスの問題をすべて回避できます。
追伸:多くの人はバイト スワップ組み込み関数を知りません。そのパフォーマンスは驚くべきものであり、浮動小数点データの場合は整数として処理されるため、さらに驚くべきものになります。バイト スワップのユースケースごとにレジスターのロードを手作業でコーディングすることなしにこれを克服する方法はありません。それを試みると、おそらくオプティマイザーでこれまでに経験したことのない大きなヒットが発生するでしょう。
他のヒント
単純に問題を再考することもできますが、これがボトルネックになるべきではありません。単純なアルゴリズムを考えてみましょう (CLI アセンブリで書かれています。ただの楽しみです)。必要な番号がローカル番号 0 にあると仮定しましょう
LDLOC 0
SHL 24
LDLOC 0
LDC.i4 0x0000ff00
SHL 8
OR
LDLOC 0
LDC.i4 0x00ff0000
SHL.UN 8
OR
LDLOC 0
SHL.UN 24
OR
これは、1 つの番号につき最大 13 (x86) アセンブリ命令です (そして、インタプリタは賢いレジスタを使用することでさらに賢くなる可能性が高くなります)。これ以上にナイーブなことはありません。
さて、それを次のコストと比較してください。
- データをロードする (作業している周辺機器も含む!)
- データの操作 (比較など)
- 結果を出力する(それが何であれ)
数値ごとに 13 命令が実行時間のかなりの部分を占めている場合は、非常に高パフォーマンスのタスクを実行していることになり、正しい形式で入力する必要があります。また、データのバッファーなどをより詳細に制御し、余分な配列境界チェックを不要にするため、おそらくマネージ言語は使用しないでしょう。
そのデータ配列がネットワーク経由で送信される場合、単なるバイト順序の反転よりもソケットの管理によりはるかに大きなコストが発生すると予想されます。ディスクからのものの場合は、このプログラムを実行する前に事前反転を検討してください。
私は2つのUINTを考慮することを考えていました ULONG型と種類
まあ、それはまた、望ましいことではないかもしれない、2つのUINT値を交換でしょう...
あなたが実際に十分を行う可能性がある、危険なモードでは、いくつかのC#コードを試みることができます。同様ます:
public static unsafe void SwapInts(uint[] data) {
int cnt = data.Length;
fixed (uint* d = data) {
byte* p = (byte*)d;
while (cnt-- > 0) {
byte a = *p;
p++;
byte b = *p;
*p = *(p + 1);
p++;
*p = b;
p++;
*(p - 3) = *p;
*p = a;
p++;
}
}
}
私のコンピュータ上でのスループットは毎秒2ギガバイトの周りでます。