C#のエンディアンネスを16ビットワードで交換するより高速な方法

StackOverflow https://stackoverflow.com/questions/1610868

  •  05-07-2019
  •  | 
  •  

質問

16ビットワードのバイトをスワップするには、これよりも高速で優れた方法が必要です。:

public static void Swap(byte[] data)
{
    for (int i = 0; i < data.Length; i += 2)
    {
        byte b = data[i];
        data[i] = data[i + 1];
        data[i + 1] = b;
    }
}

アイデアはありますか?

役に立ちましたか?

解決

Uberhacker賞を申請するために、以下を提出します。私のテストでは、8,192バイトのSource配列を使用し、 SwapX2 を100,000回呼び出しました:

public static unsafe void SwapX2(Byte[] source)  
{  
    fixed (Byte* pSource = &source[0])  
    {  
        Byte* bp = pSource;  
        Byte* bp_stop = bp + source.Length;  

        while (bp < bp_stop)  
        {
            *(UInt16*)bp = (UInt16)(*bp << 8 | *(bp + 1));  
            bp += 2;  
        }  
    }  
}

私のベンチマークでは、このバージョンは元の質問で送信されたコードよりも1.8倍以上高速であることを示しています。

他のヒント

この方法は、元の質問の方法よりもわずかに速いようです:

private static byte[] _temp = new byte[0];
public static void Swap(byte[] data)
{
    if (data.Length > _temp.Length)
    {
        _temp = new byte[data.Length];
    }
    Buffer.BlockCopy(data, 1, _temp, 0, data.Length - 1);
    for (int i = 0; i < data.Length; i += 2)
    {
        _temp[i + 1] = data[i];
    }
    Buffer.BlockCopy(_temp, 0, data, 0, data.Length);
}

私のベンチマークでは、メソッドが繰り返し呼び出されることを想定していたため、 _temp 配列のサイズ変更は要素ではありません。このメソッドは、バイトスワップの半分を最初の Buffer.BlockCopy(...)呼び出し(ソース位置オフセット1)で実行できるという事実に依存しています。

完全に心を失った場合に備えて、これを自分でベンチマークしてください。私のテストでは、このメソッドは元のメソッド(ループの外で byte b を宣言するために修正した)の約70%かかります。

私はいつもこれが好きだった:

public static Int64 SwapByteOrder(Int64 value) 
{
  var uvalue = (UInt64)value;
  UInt64 swapped = 
       ( (0x00000000000000FF) & (uvalue >> 56)
       | (0x000000000000FF00) & (uvalue >> 40)
       | (0x0000000000FF0000) & (uvalue >> 24)
       | (0x00000000FF000000) & (uvalue >> 8)
       | (0x000000FF00000000) & (uvalue << 8)
       | (0x0000FF0000000000) & (uvalue << 24)
       | (0x00FF000000000000) & (uvalue << 40)
       | (0xFF00000000000000) & (uvalue << 56));
  return (Int64)swapped;
}

これは最速の方法であり、かなり読みやすく安全であることがわかると思います。これは明らかに64ビット値に適用されますが、32または16にも同じ手法を使用できます。

次の方法は、私のテストでは、受け入れられた答えのほぼ3倍高速です。 (3文字以上または6バイト以上では常に高速、3文字以下または6バイト以下ではやや遅くなります。)(受け入れられた回答は配列の境界外で読み書きできることに注意してください。

(更新ポインタを持っている間、プロパティを呼び出して長さを取得する必要はありません。そのポインタの使用は少し高速ですが、ランタイムチェックまたは次の例のように、各プラットフォーム用にビルドするプロジェクト構成が必要です。各構成でX86およびX64を定義します。)

static unsafe void SwapV2(byte[] source)
{
    fixed (byte* psource = source)
    {
#if X86
        var length = *((uint*)(psource - 4)) & 0xFFFFFFFEU;
#elif X64
        var length = *((uint*)(psource - 8)) & 0xFFFFFFFEU;
#else
        var length = (source.Length & 0xFFFFFFFE);
#endif
        while (length > 7)
        {
            length -= 8;
            ulong* pulong = (ulong*)(psource + length);
            *pulong = ( ((*pulong >> 8) & 0x00FF00FF00FF00FFUL)
                      | ((*pulong << 8) & 0xFF00FF00FF00FF00UL));
        }
        if(length > 3)
        {
            length -= 4;
            uint* puint = (uint*)(psource + length);
            *puint = ( ((*puint >> 8) & 0x00FF00FFU)
                     | ((*puint << 8) & 0xFF00FF00U));
        }
        if(length > 1)
        {
            ushort* pushort = (ushort*)psource;
            *pushort = (ushort) ( (*pushort >> 8)
                                | (*pushort << 8));
        }
    }
}

8192バイトの300.000倍の5つのテスト

  • SwapV2:1055、1051、1043、1041、1044
  • SwapX2:2802、2803、2803、2805、2805

50.000.000×6バイトの5つのテスト

  • SwapV2:1092、1085、1086、1087、1086
  • SwapX2:1018、1019、1015、1017、1018

ただし、データが大きく、パフォーマンスが本当に重要な場合は、SSEまたはAVXを使用できます。 (13倍高速) https://pastebin.com/WaFk275U

8192バイトまたは4096文字で5回、100000ループをテストします

  • SwapX2:226、223、225、226、227最小:223
  • SwapV2:113、111、112、114、112最小:111
  • SwapA2:17、17、17、17、17、16分:16

まあ、 XORスワッピングトリックを使用して、中間バイトを回避できます。ただし、それ以上速くなることはありません。ILがまったく同じであっても驚かないでしょう。

for (int i = 0; i < data.Length; i += 2)
{
    data[i] ^= data[i + 1];
    data[i + 1] ^= data[i];
    data[i] ^= data[i + 1];
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top