26

バイナリデータを読み取る必要があるかなり大きなファイルがあるという状況に遭遇しました。

その結果、.NETのデフォルトのBinaryReader実装はかなり遅いことに気づきました。.NET Reflectorでそれを見たとき、私はこれに出くわしました:

public virtual int ReadInt32()
{
    if (this.m_isMemoryStream)
    {
        MemoryStream stream = this.m_stream as MemoryStream;
        return stream.InternalReadInt32();
    }
    this.FillBuffer(4);
    return (((this.m_buffer[0] | (this.m_buffer[1] << 8)) | (this.m_buffer[2] << 0x10)) | (this.m_buffer[3] << 0x18));
}

32ビットCPUが発明されて以来、コンピュータが32ビット値で動作するように設計されていることを考えると、これは非常に非効率的だと思います。

そこで、代わりに次のようなコードを使用して、独自の(安全でない)FastBinaryReaderクラスを作成しました。

public unsafe class FastBinaryReader :IDisposable
{
    private static byte[] buffer = new byte[50];
    //private Stream baseStream;

    public Stream BaseStream { get; private set; }
    public FastBinaryReader(Stream input)
    {
        BaseStream = input;
    }


    public int ReadInt32()
    {
        BaseStream.Read(buffer, 0, 4);

        fixed (byte* numRef = &(buffer[0]))
        {
            return *(((int*)numRef));
        }
    }
...
}

これははるかに高速です-500MBのファイルを読み取るのにかかる時間を5〜7秒短縮できましたが、それでも全体的にかなり遅いです(最初は29秒、現在は約22秒FastBinaryReader)。

このような比較的小さなファイルを読み取るのにまだ時間がかかる理由については、まだ少し戸惑っています。あるディスクから別のディスクにファイルをコピーする場合、数秒しかかからないため、ディスクスループットは問題になりません。

さらに、ReadInt32などの呼び出しをインライン化すると、次のコードになりました。

using (var br = new FastBinaryReader(new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan)))

  while (br.BaseStream.Position < br.BaseStream.Length)
  {
      var doc = DocumentData.Deserialize(br);
      docData[doc.InternalId] = doc;
  }
}

   public static DocumentData Deserialize(FastBinaryReader reader)
   {
       byte[] buffer = new byte[4 + 4 + 8 + 4 + 4 + 1 + 4];
       reader.BaseStream.Read(buffer, 0, buffer.Length);

       DocumentData data = new DocumentData();
       fixed (byte* numRef = &(buffer[0]))
       {
           data.InternalId = *((int*)&(numRef[0]));
           data.b = *((int*)&(numRef[4]));
           data.c = *((long*)&(numRef[8]));
           data.d = *((float*)&(numRef[16]));
           data.e = *((float*)&(numRef[20]));
           data.f = numRef[24];
           data.g = *((int*)&(numRef[25]));
       }
       return data;
   }

これをさらに速くする方法についてのさらなるアイデアはありますか?データは線形で、サイズが固定されており、シーケンシャルであるため、マーシャリングを使用して、ファイル全体をカスタム構造の上にあるメモリに直接マップできるのではないかと考えていました。

解決済み: FileStreamのbuffering/BufferedStreamに欠陥があるという結論に達しました。以下の承認された回答と私自身の回答(解決策を含む)を参照してください。

4

4 に答える 4

20

BinaryReader / FileStreamで同様のパフォーマンスの問題が発生し、プロファイリングした後、問題はFileStreamバッファリングではなく、次の行にあることがわかりました。

while (br.BaseStream.Position < br.BaseStream.Length) {

具体的には、のプロパティbr.BaseStream.Lengthは、FileStream各ループのファイルサイズを取得するために(比較的)遅いシステムコールを行います。コードをこれに変更した後:

long length = br.BaseStream.Length;
while (br.BaseStream.Position < length) {

に適切なバッファサイズを使用しFileStreamて、例と同様のパフォーマンスを達成しましたMemoryStream

于 2012-04-26T23:39:19.857 に答える
11

興味深いことに、ファイル全体をバッファに読み込んでメモリ内で処理することで、大きな違いが生まれました。これはメモリを犠牲にしますが、十分にあります。

これにより、FileStream(またはさらに言えばBufferedStream)のバッファー実装に欠陥があると思います。これは、どのサイズのバッファーを試しても、パフォーマンスが低下するためです。

  using (var br = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan))
  {
      byte[] buffer = new byte[br.Length];
      br.Read(buffer, 0, buffer.Length);
      using (var memoryStream = new MemoryStream(buffer))
      {
          while (memoryStream.Position < memoryStream.Length)
          {
              var doc = DocumentData.Deserialize(memoryStream);
              docData[doc.InternalId] = doc;
          }
      }
  }

22秒から2〜5秒(私が推測しているディスクキャッシュによって異なります)になりました。これで十分です。

于 2009-08-06T12:21:53.140 に答える
10

ファイルコピーを実行すると、大量のデータが読み取られてディスクに書き込まれます。

一度に4バイトずつファイル全体を読み取っています。これは遅くなるはずです。ストリームの実装がバッファリングするのに十分賢い場合でも、少なくとも500 MB / 4 =131072000API呼び出しがあります。

大量のデータを読み取り、それを順番に調べて、ファイルが処理されるまで繰り返す方が賢明ではありませんか?

于 2009-08-06T11:59:08.307 に答える
5

1つの警告; CPUのエンディアンを再確認することをお勧めします...リトルエンディアンが完全に安全ではないと仮定します(チタンなどを考えてください)。

また、違いが生じるかどうかを確認することもできます(違いがあるかどうかはわかりBufferedStreamません)。

于 2009-08-06T11:50:37.433 に答える