【发布时间】:2021-08-03 19:18:25
【问题描述】:
我有一个 CSV(逗号分隔值)格式的数据文件,其中包含大约 5000 万行。
每一行都被读入一个字符串,解析,然后用于填充 FOO 类型对象的字段。然后该对象被添加到最终有 5000 万个项目的 List(of FOO) 中。
一切正常,并且适合内存(至少在 x64 机器上),但速度很慢。每次加载并将文件解析到列表中大约需要 5 分钟。我想让它更快。 我怎样才能让它更快?
代码的重要部分如下所示。
Public Sub LoadCsvFile(ByVal FilePath As String)
Dim s As IO.StreamReader = My.Computer.FileSystem.OpenTextFileReader(FilePath)
'Find header line
Dim L As String
While Not s.EndOfStream
L = s.ReadLine()
If L = "" Then Continue While 'discard blank line
Exit While
End While
'Parse data lines
While Not s.EndOfStream
L = s.ReadLine()
If L = "" Then Continue While 'discard blank line
Dim T As FOO = FOO.FromCSV(L)
Add(T)
End While
s.Close()
End Sub
Public Class FOO
Public time As Date
Public ID As UInt64
Public A As Double
Public B As Double
Public C As Double
Public Shared Function FromCSV(ByVal X As String) As FOO
Dim T As New FOO
Dim tokens As String() = X.Split(",")
If Not DateTime.TryParse(tokens(0), T.time) Then
Throw New Exception("Could not convert CSV to FOO: Invalid ISO 8601 timestamp")
End If
If Not UInt64.TryParse(tokens(1), T.ID) Then
Throw New Exception("Could not convert CSV to FOO: Invalid ID")
End If
If Not Double.TryParse(tokens(2), T.A) Then
Throw New Exception("Could not convert CSV to FOO: Invalid Format for A")
End If
If Not Double.TryParse(tokens(3), T.B) Then
Throw New Exception("Could not convert CSV to FOO: Invalid Format for B")
End If
If Not Double.TryParse(tokens(4), T.C) Then
Throw New Exception("Could not convert CSV to FOO: Invalid Format for C")
End If
Return T
End Function
End Class
我做了一些基准测试,结果如下。
- 上面的完整算法需要 314 秒来加载整个文件并将对象放入列表中。
- FromCSV() 的主体被简化为只返回一个 FOO 类型的新对象和默认字段值,整个过程耗时 84 秒。因此,将文本行处理到对象字段中似乎需要 230 秒(占总时间的 73%)。
- 除了解析 ISO 8601 日期字符串之外的所有操作都需要 175 秒。因此,处理日期字符串似乎需要 139 秒,这是文本处理时间的 60%,仅针对该字段。
- 仅读取文件中的行而不进行任何处理或创建对象需要 41 秒。
- 使用 StreamReader.ReadBlock 以大约 1KB 的块读取整个文件需要 24 秒,但它在总体方案上的改进很小,可能不值得增加复杂性。为了使用 TryParse,我现在需要手动创建临时字符串,而不是使用 String.Split()。
此时我看到的唯一途径是每隔几秒钟向用户显示一次状态,这样他们就不会怀疑程序是否被冻结或其他原因。
更新
我创建了两个新功能。可以使用 System.IO.BinaryWriter 将数据集从内存保存到二进制文件中。另一个函数可以使用 System.IO.BinaryReader 将该二进制文件加载回内存。二进制版本比 CSV 版本快得多,而且二进制文件占用的空间要少得多。
以下是基准测试结果(所有测试使用相同的数据集):
- 加载 CSV:340 秒
- 保存 CSV:312 秒
- 保存箱:29 秒
- 装载箱:41 秒
- CSV 文件大小:3.86GB
- BIN 文件大小:1.63GB
【问题讨论】:
-
也许您可以尝试解析Parallel.For Loop 中的每一行。不过,您可能需要先将所有行加载到列表或数组中。
-
@user4574 你试过
TryParseExact并给它日期的格式吗?它可能会使它稍微快一点。此外,如果数据是机器生成的,使用Parse而不是TryParse可能会有所帮助,但也只是一点点帮助。
标签: vb.net performance csv file-io