3

簡単なタスクがあります。ある数値 (バイト配列の長さ) をバイト配列にエンコードし、最終的な値をエンコードするために必要なバイト数を決定します (この記事の実装: Encoded Length and Value Bytes )。

もともと私はタスクを達成する簡単な方法を書きました:

public static Byte[] Encode(Byte[] rawData, Byte enclosingtag) {
    if (rawData == null) {
        return new Byte[] { enclosingtag, 0 };
    }
    List<Byte> computedRawData = new List<Byte> { enclosingtag };
    // if array size is less than 128, encode length directly. No questions here
    if (rawData.Length < 128) {
        computedRawData.Add((Byte)rawData.Length);
    } else {
        // convert array size to a hex string
        String hexLength = rawData.Length.ToString("x2");
        // if hex string has odd length, align it to even by prepending hex string
        // with '0' character
        if (hexLength.Length % 2 == 1) { hexLength = "0" + hexLength; }
        // take a pair of hex characters and convert each octet to a byte
        Byte[] lengthBytes = Enumerable.Range(0, hexLength.Length)
                .Where(x => x % 2 == 0)
                .Select(x => Convert.ToByte(hexLength.Substring(x, 2), 16))
                .ToArray();
        // insert padding byte, set bit 7 to 1 and add byte count required
        // to encode length bytes
        Byte paddingByte = (Byte)(128 + lengthBytes.Length);
        computedRawData.Add(paddingByte);
        computedRawData.AddRange(lengthBytes);
    }
    computedRawData.AddRange(rawData);
    return computedRawData.ToArray();
}

これは古いコードで、ひどい方法で書かれています。

BitConverter現在、ビットごとの演算子またはクラスのいずれかを使用してコードを最適化しようとしています。以下は、ビットごとのエディションの例です。

public static Byte[] Encode2(Byte[] rawData, Byte enclosingtag) {
    if (rawData == null) {
        return new Byte[] { enclosingtag, 0 };
    }
    List<Byte> computedRawData = new List<Byte>(rawData);
    if (rawData.Length < 128) {
        computedRawData.Insert(0, (Byte)rawData.Length);
    } else {
        // temp number
        Int32 num = rawData.Length;
        // track byte count, this will be necessary further
        Int32 counter = 1;
        // simply make bitwise AND to extract byte value
        // and shift right while remaining value is still more than 255
        // (there are more than 8 bits)
        while (num >= 256) {
            counter++;
            computedRawData.Insert(0, (Byte)(num & 255));
            num = num >> 8;
        }
        // compose final array
        computedRawData.InsertRange(0, new[] { (Byte)(128 + counter), (Byte)num });
    }
    computedRawData.Insert(0, enclosingtag);
    return computedRawData.ToArray();
}

クラスを使用した最終的な実装BitConverter

public static Byte[] Encode3(Byte[] rawData, Byte enclosingtag) {
    if (rawData == null) {
        return new Byte[] { enclosingtag, 0 };
    }
    List<Byte> computedRawData = new List<Byte>(rawData);
    if (rawData.Length < 128) {
        computedRawData.Insert(0, (Byte)rawData.Length);
    } else {
        // convert integer to a byte array
        Byte[] bytes = BitConverter.GetBytes(rawData.Length);
        // start from the end of a byte array to skip unnecessary zero bytes
        for (int i = bytes.Length - 1; i >= 0; i--) {
            // once the byte value is non-zero, take everything starting
            // from the current position up to array start.
            if (bytes[i] > 0) {
                // we need to reverse the array to get the proper byte order
                computedRawData.InsertRange(0, bytes.Take(i + 1).Reverse());
                // compose final array
                computedRawData.Insert(0, (Byte)(128 + i + 1));
                computedRawData.Insert(0, enclosingtag);
                return computedRawData.ToArray();
            }
        }
    }
    return null;
}

すべてのメソッドは期待どおりに機能します。ストップウォッチ クラスページの例を使用して、パフォーマンスを測定しました。そしてパフォーマンステストは私を驚かせました。私のテスト メソッドは、100,000 要素のバイト配列 (実際には配列 sixe のみ) をエンコードするメソッドを 1000 回実行し、平均時間は次のとおりです。

  • エンコード -- 約 200ms
  • Encode2 -- 約 270ms
  • Encode3 -- 約 320ms

個人的には methodの方Encode2がコードが読みやすいので気に入っていますが、パフォーマンスはあまり良くありません。

Encode2質問:メソッドのパフォーマンスを改善するため、または読みやすさを改善するために何を提案しますEncodeか?

どんな助けでも大歓迎です。

===========================

更新: このスレッドに参加してくれたすべての人に感謝します。私はすべての提案を考慮に入れ、この解決策に行き着きました:

public static Byte[] Encode6(Byte[] rawData, Byte enclosingtag) {
    if (rawData == null) {
        return new Byte[] { enclosingtag, 0 };
    }
    Byte[] retValue;
    if (rawData.Length < 128) {
        retValue = new Byte[rawData.Length + 2];
        retValue[0] = enclosingtag;
        retValue[1] = (Byte)rawData.Length;
    } else {
        Byte[] lenBytes = new Byte[3];
        Int32 num = rawData.Length;
        Int32 counter = 0;
        while (num >= 256) {
            lenBytes[counter] = (Byte)(num & 255);
            num >>= 8;
            counter++;
        }
        // 3 is: len byte and enclosing tag
        retValue = new byte[rawData.Length + 3 + counter];
        rawData.CopyTo(retValue, 3 + counter);
        retValue[0] = enclosingtag;
        retValue[1] = (Byte)(129 + counter);
        retValue[2] = (Byte)num;
        Int32 n = 3;
        for (Int32 i = counter - 1; i >= 0; i--) {
            retValue[n] = lenBytes[i];
            n++;
        }
    }
    return retValue;
}

最終的に、リストから固定サイズのバイト配列に移行しました。同じデータセットに対する平均時間は、現在約 65 ミリ秒です。リスト (ビット演算ではない) を使用すると、パフォーマンスが大幅に低下するようです。

4

2 に答える 2

2

ここでの主な問題は、ほとんどの場合、リストの割り当てであり、新しい要素を挿入するとき、および最後にリストが配列に変換されるときに必要な割り当てです。このコードは、ほとんどの時間をガベージ コレクターとメモリ アロケーターで費やしている可能性があります。ビット演算子の使用と非使用は、比較するとおそらくほとんど意味がありません。最初に割り当てるメモリの量を減らす方法を調べたでしょう。

1 つの方法は、データを割り当てて返す代わりに、事前に割り当てられたバイト配列への参照と、配列内の場所へのインデックスを送信し、書き込んだバイト数を示す整数を返すことです。通常、大きな配列で作業する方が、多くの小さなオブジェクトで作業するよりも効率的です。他の人が述べたように、プロファイラーを使用して、コードがどこで時間を費やしているかを確認してください。

当然のことながら、私が言及した最適化により、コードは本質的により低レベルになり、C で通常行うことにより近くなりますが、読みやすさとパフォーマンスの間には多くの場合トレードオフがあります。

于 2015-03-01T11:15:13.910 に答える
2

「前に挿入」の代わりに「反転、追加、反転」を使用し、すべてを事前に割り当てると、次のようになります: (未テスト)

public static byte[] Encode4(byte[] rawData, byte enclosingtag) {
    if (rawData == null) {
        return new byte[] { enclosingtag, 0 };
    }
    List<byte> computedRawData = new List<byte>(rawData.Length + 6);
    computedRawData.AddRange(rawData);
    if (rawData.Length < 128) {
        computedRawData.InsertRange(0, new byte[] { enclosingtag, (byte)rawData.Length });
    } else {
        computedRawData.Reverse();
        // temp number
        int num = rawData.Length;
        // track byte count, this will be necessary further
        int counter = 1;
        // simply cast to byte to extract byte value
        // and shift right while remaining value is still more than 255
        // (there are more than 8 bits)
        while (num >= 256) {
            counter++;
            computedRawData.Add((byte)num);
            num >>= 8;
        }
        // compose final array
        computedRawData.Add((byte)num);
        computedRawData.Add((byte)(counter + 128));
        computedRawData.Add(enclosingtag);
        computedRawData.Reverse();
    }
    return computedRawData.ToArray();
}

それがより速くなるかどうかはわかりませんが、それは理にかなっています.1つしかない場合を除いて、コストのかかる「前に挿入」操作はほとんど回避されます(おそらく十分ではありません) 2 つのリバースとのバランス)。

もう 1 つのアイデアは、別の方法で挿入を前に 1 回だけに制限することです。そこに挿入する必要があるものをすべて集めてから、1 回だけ実行します。次のようになります: (未テスト)

public static byte[] Encode5(byte[] rawData, byte enclosingtag) {
    if (rawData == null) {
        return new byte[] { enclosingtag, 0 };
    }
    List<byte> computedRawData = new List<byte>(rawData);
    if (rawData.Length < 128) {
        computedRawData.InsertRange(0, new byte[] { enclosingtag, (byte)rawData.Length });
    } else {
        // list of all things that will be inserted
        List<byte> front = new List<byte>(8);
        // temp number
        int num = rawData.Length;
        // track byte count, this will be necessary further
        int counter = 1;
        // simply cast to byte to extract byte value
        // and shift right while remaining value is still more than 255
        // (there are more than 8 bits)
        while (num >= 256) {
            counter++;
            front.Insert(0, (byte)num);  // inserting in tiny list, not so bad
            num >>= 8;
        }
        // compose final array
        front.InsertRange(0, new[] { (byte)(128 + counter), (byte)num });
        front.Insert(0, enclosingtag);
        computedRawData.InsertRange(0, front);
    }
    return computedRawData.ToArray();
}

それが十分でない場合や役に立たなかった場合 (またはこれがさらに悪い場合は、そうかもしれません)、さらにアイデアを考えてみます。

于 2015-03-01T11:22:48.063 に答える