2

何百万ものレコードを含む大きなバイナリ ファイルを読みたいと思っており、レコードのレポートを取得したいと考えています。私は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両方を使用しようとしました。テストモジュールは次のとおりです。GetAllIEnumerable

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 での反復は本当にこのパフォーマンス コストを持っているのでしょうか、それとも何かを逃したのでしょうか?

4

1 に答える 1

2

はい、イテレータ関数を使用すると、パフォーマンスが低下します。

あなたのコードをコンパイルしたところ、あなたと同じ結果が得られました。生成された IL コードを見てみました。GetAll メソッドから作成されたステート マシンには多くのものが含まれていますが、ほとんどの命令は nop または単純な操作です。

あなたが言うように、イテレータ関数を使用した場合と使用しない場合の結果は 25% 異なります。それは多すぎません。StartReadBinary を使用している場合、(イベントを介して) OnByteRead メソッドを 30 億回呼び出す大きなサイクルが 1 つだけあります。ただし、foreach サイクルでオブジェクトを作成する場合、各オブジェクトに対して行う必要があるのは、生成された列挙子の GetCurrent() メソッドと MoveNext() メソッドを呼び出すことです。後者は簡単ではありません (GetAll のコードのほとんどはそこに移動しました)、かなりの量のコンパイラ生成変数を使用します。

"Yield" を使用すると、コンパイラはステート マシンを表すために複雑な IL コードを作成する必要があるため、通常、プログラムの速度が低下します。

于 2014-01-24T21:39:02.540 に答える