何百万ものレコードを含む大きなバイナリ ファイルを読みたいと思っており、レコードのレポートを取得したいと考えています。私はBinaryReader
読み取り(リーダーで最高のパフォーマンスを発揮すると思います)を使用し、読み取りバイトをデータモデルに変換します。レコードの数が多いため、モデルをレポート レイヤーに渡すことは別の問題ですIEnumerable
。レポートを開発するときは、LINQ の機能と機能を使用することを好みます。
サンプル データ クラスは次のとおりです。
Public Class MyData
Public A1 As UInt64
Public A2 As UInt64
Public A3 As Byte
Public A4 As UInt16
Public A5 As UInt64
End Class
このサブを使用してファイルを作成しました:
Sub CreateSampleFile()
Using streamWriter As New FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.Write)
For i As Integer = 1 To 1000
For j As Integer = 1 To 1000
For k = 1 To 30
Dim item As New MyData With {.A1 = i, .A2 = j, .A3 = k, .A4 = j, .A5 = i * j}
Dim bytes() As Byte = BitConverter.GetBytes(item.A1).Concat(BitConverter.GetBytes(item.A2)).Concat({item.A3}).Concat(BitConverter.GetBytes(item.A4)).Concat(BitConverter.GetBytes(item.A5)).ToArray
streamWriter.Write(bytes, 0, bytes.Length)
Next
Next
Next
End Using
End Sub
そして、ここに私のリーダークラスがあります:
Imports System.IO
Public Class FileReader
Public Const BUFFER_LENGTH As Long = 4096 * 256 * 27
Public Const MY_DATA_LENGTH As Long = 27
Private _buffer(BUFFER_LENGTH - 1) As Byte
Private _streamWriter As FileStream
Public Event OnByteRead(sender As FileReader, bytes() As Byte, index As Long)
Public Sub StartReadBinary(fileName As String)
Dim currentBufferReadCount As Long = 0
Using fileStream As New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)
Using streamReader As New BinaryReader(fileStream)
currentBufferReadCount = streamReader.Read(Me._buffer, 0, Me._buffer.Length)
While currentBufferReadCount > 0
For i As Integer = 0 To currentBufferReadCount - 1 Step MY_DATA_LENGTH
RaiseEvent OnByteRead(Me, Me._buffer, i)
Next
currentBufferReadCount = streamReader.Read(Me._buffer, 0, Me._buffer.Length)
End While
End Using
End Using
End Sub
Public Iterator Function GetAll(fileName As String) As IEnumerable(Of MyData)
Dim currentBufferReadCount As Long = 0
Using fileStream As New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)
Using streamReader As New BinaryReader(fileStream)
currentBufferReadCount = streamReader.Read(Me._buffer, 0, Me._buffer.Length)
While currentBufferReadCount > 0
For i As Integer = 0 To currentBufferReadCount - 1 Step MY_DATA_LENGTH
Yield GetInstance(_buffer, i)
Next
currentBufferReadCount = streamReader.Read(Me._buffer, 0, Me._buffer.Length)
End While
End Using
End Using
End Function
Public Function GetInstance(bytes() As Byte, index As Long) As MyData
Return New MyData With {.A1 = BitConverter.ToUInt64(bytes, index), .A2 = BitConverter.ToUInt64(bytes, index + 8), .A3 = bytes(index + 16), .A4 = BitConverter.ToUInt16(bytes, index + 17), .A5 = BitConverter.ToUInt64(bytes, index + 19)}
End Function
End Class
パフォーマンスを考えていたので、ファイルから読み取ったレコードごとにメソッドとしてのメソッドとイベントの発生のIEnumerable
両方を使用しようとしました。テストモジュールは次のとおりです。GetAll
IEnumerable
Imports System.IO
Module Module1
Private fileName As String = "MyData.dat"
Private readerJustTraverse As New FileReader
Private WithEvents readerWithoutInstance As New FileReader
Private WithEvents readerWithInstance As New FileReader
Private readerIEnumerable As New FileReader
Sub Main()
Dim s As New Stopwatch
s.Start()
readerJustTraverse.StartReadBinary(fileName)
s.Stop()
Console.WriteLine("Read bytes: {0}", s.ElapsedMilliseconds)
s.Restart()
readerWithoutInstance.StartReadBinary(fileName)
s.Stop()
Console.WriteLine("Read bytes, raise event: {0}", s.ElapsedMilliseconds)
s.Restart()
readerWithInstance.StartReadBinary(fileName)
s.Stop()
Console.WriteLine("Read bytes, raise event, get instance: {0}", s.ElapsedMilliseconds)
s.Restart()
For Each item In readerIenumerable.GetAll(fileName)
Next
Console.WriteLine("Read bytes, get instance, return yield: {0}", s.ElapsedMilliseconds)
s.Stop()
Console.ReadLine()
End Sub
Private Sub readerWithInstance_OnByteRead(sender As FileReader, bytes() As Byte, index As Long) Handles readerWithInstance.OnByteRead
Dim item As MyData = sender.GetInstance(bytes, index)
End Sub
Private Sub readerWithoutInstance_OnByteRead(sender As FileReader, bytes() As Byte, index As Long) Handles readerWithoutInstance.OnByteRead
'do nothing
End Sub
End Module
私が疑問に思っているのは、各プロセスの経過時間です。テスト結果は次のとおりです (ASUS Ultrabook - Zenbook Core i7 でテスト):
読み取りバイト: 384 (読み取りバイトに触れずに!)
バイトの読み取り、イベントの発生: 583
バイトの読み取り、イベントの発生、インスタンスの取得: 3923
バイトを読み取り、インスタンスを取得し、yield を返す: 4917
ファイルをバイトとして読み取るのは非常に高速であり、バイトをモデルに変換するのは遅いことを示しています。また、IEnumerable の結果を取得する代わりにイベントを発生させると、25% 高速になります。
IEnumerable での反復は本当にこのパフォーマンス コストを持っているのでしょうか、それとも何かを逃したのでしょうか?