バイナリ データを読み取る必要がある非常に大きなファイルがある状況に遭遇しました。
その結果、.NETのデフォルトのBinaryReader実装はかなり遅いことに気づきました。.NET リフレクター私はこれに遭遇しました:
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));
}
}
...
}
これはかなり高速です。500 MB のファイルを読み取るのにかかる時間を 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 のバッファリング/BufferedStream に欠陥があるという結論に達しました。以下の承認済みの回答と私自身の回答 (解決策付き) を参照してください。
ベストアンサー1
BinaryReader/FileStream でも同様のパフォーマンスの問題に遭遇しましたが、プロファイリングを行った結果、問題はFileStream
バッファリングではなく、次の行にあることがわかりました。
while (br.BaseStream.Position < br.BaseStream.Length) {
br.BaseStream.Length
具体的には、 a のプロパティFileStream
は、各ループでファイル サイズを取得するために (比較的) 遅いシステム コールを実行します。コードを次のように変更すると、
long length = br.BaseStream.Length;
while (br.BaseStream.Position < length) {
適切なバッファ サイズを使用することでFileStream
、例と同様のパフォーマンスを実現しましたMemoryStream
。