2009-08-06 27 views
22

私はバイナリデータを読み込む必要がある非常に大きなファイルがある状況に遭遇しました。より速い(安全でない)BinaryReader in .NET

したがって、.NETのデフォルトのBinaryReaderの実装がかなり遅いことに気付きました。 .NET Reflectorでそれを見ている時に私はこれに出くわした:コンピュータは32ビットCPUが発明されて以来32ビット値で動作するように設計された方法で考えて、非常に非効率的なとして私を打つ

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)); 
} 

を。私はオフ時間5-7秒をオフに剃ることに成功し、それを読むために取った - はるかに高速され

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)); 
     } 
    } 
... 
} 

は:

は、だから私は、代わりにこのようなコードで自分の(危険な)FastBinaryReaderクラスを作りました500  のMBファイルですが、全体的にかなり遅いです(最初は29秒、現在は FastBinaryReaderで22秒です)。

これは比較的小さいファイルを読むのになぜそれほど時間がかかりますかというと、それでも私にはうんざりです。あるディスクから別のディスクにファイルをコピーすると、数秒しかかからないため、ディスクのスループットは問題になりません。

私はさらに、などの呼び出しを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のバッファリング/ BufferedStreamに欠陥があると判断しました。以下の回答と私自身の解答(解答あり)をご覧ください。

+0

参考になるかもしれません:http://stackoverflow.com/questions/19558435/what-is-the-best-buffer-size-when-using-binaryreader-to-read-big-files-1gb/19837238? noredirect = 1#19837238 –

答えて

9

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

ファイル全体を一度に4バイトずつ読み込みます。これは遅くなることが拘束されます。ストリームのインプリメンテーションがバッファになるほどスマートであっても、依然として少なくとものMB/4 = 131072000 APIコールがあります。

大量のデータを読み込んだ後、順次処理し、ファイルが処理されるまで繰り返すのはもっと賢明ではありませんか?

+1

FileStreamコンストラクタには、バッファサイズを指定するパラメータがあります。実際には読み込みはチャンクで行われます。私はバッファサイズにさまざまな値を試しましたが、大きな改善はありませんでした。非常に大きなバッファサイズは実際に私の測定で性能を傷つけます。 – andreialecu

+0

あなたはまだ 'ReadInt32'への膨大な呼び出しを行っています。連続したメモリから自分自身を取得するだけで、はるかに高速になります。 – Toad

+0

質問をもう一度読んでください。私は実際の実装でReadInt32を使用していません。オブジェクトごとに1つしか読み込まれず、すべての変換がインライン展開されています。 – andreialecu

5

警告: CPU's endianness ...リトルエンディアンがではないとすれば、かなりであると思うかもしれない(と思う:itaniumなど)。

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

+0

私はエンディアンの問題を認識していますが、これは私が配備を完全に制御できる独自のアプリケーションです。 BufferedStreamに関して、私の理解から、FileStreamは既にバッファされているので、不要な中間バッファを追加するだけです。 P.S .:私はこのプロジェクトでもあなたのprotobufライブラリを使用していますので、多くのありがとうございます:) – andreialecu

+3

私はちょうどラッピングBufferedStreamで新しい測定を行い、予想通り、違いはありません。 – andreialecu

9

興味深いことに、ファイル全体をバッファに読み込んでメモリに読み込むと大きな違いがありました。これは記憶を犠牲にしていますが、十分に持っています。

これは、私が試みたサイズバッファが何であっても、パフォーマンスがまだ吸い込まれているため、FileStream(またはその点でBufferedStream)のバッファ実装に欠陥があると思います。2-5秒にダウン

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から今(ディスクキャッシュ、私は推測しているに依存します)。

+0

私の答えはそれが欠陥ではなかった; ^) – Toad

+3

ありがとう。しかし実際には、.NETのバッファ実装には問題があります。なぜなら、ファイルサイズ(中間のMemoryStreamに相当するはずのはずのはずのサイズ)と同じ大きさのバッファサイズを試してみました。理論的には、あなたの提案は冗長であったはずですが、実際には大当たりです。 – andreialecu

+6

あなたは単にvar buffer = File.ReadAllBytes(cacheFilePath);と言うことができます。いくつかのコードを保存し、はるかに高速です – gjvdkamp

16

私はBinaryReader/FileStreamを有する同様のパフォーマンスの問題に走った、とプロファイリングの後、私はこの問題は、FileStreamバッファリングしていないことを発見したが、代わりにこのラインを持つ:

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

具体的には、プロパティをbr.BaseStream.LengthFileStream上では、(比較的)遅いシステムコールが各ループのファイルサイズを取得します。このようにコードを変更した後:

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

およびFileStreamための適切なバッファサイズを使用して、IはMemoryStream例と同様の性能を達成しました。

関連する問題