C#二进制读取器性能成本替代方案
也许这个问题已经被问过很多次了,但仍然在为这个问题而挣扎 场景:C#库,其中包含一个解释器/解析器,该解释器/解析器打开文件并对其进行解释/解析,大量使用以下逻辑实现:C#二进制读取器性能成本替代方案,c#,.net,performance,binaryreader,C#,.net,Performance,Binaryreader,也许这个问题已经被问过很多次了,但仍然在为这个问题而挣扎 场景:C#库,其中包含一个解释器/解析器,该解释器/解析器打开文件并对其进行解释/解析,大量使用以下逻辑实现: BinaryReader .PeekChar() .Read() .ReadBytes() .ReadByte() .BaseStream.Position .BaseStream.Length BinaryReader从文件流接收it流。文件大小可以是几KB、MB甚至GB。 提取的代码片段: using (Strea
BinaryReader
.PeekChar()
.Read()
.ReadBytes()
.ReadByte()
.BaseStream.Position
.BaseStream.Length
BinaryReader从文件流接收it流。文件大小可以是几KB、MB甚至GB。
提取的代码片段:
using (Stream s = File.OpenRead(path)) { // ...
using (var br = new BinaryReader(context.Input, new ASCIIEncoding())) { // ...
// context.Input:
public Stream Input { get; set; }
解析一个3MB的文件大约需要20秒,这非常慢。使用BinaryReader.Read()和PeekChar()函数浪费的时间最多
我尝试尽可能地优化,在循环中使用时缓存例如br.BaseStream.Length。最大的问题仍然是.Read()和PeekChar()调用太多,我无法更改这部分逻辑
接下来,我想增加文件流的缓冲区:
using (Stream s = new FileStream(pclFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096)) // profiler time: 13784
using (Stream s = new FileStream(pclFile, FileMode.Open, FileAccess.Read, FileShare.Read, 8192)) // profiler time: 13863
using (Stream s = new FileStream(pclFile, FileMode.Open, FileAccess.Read, FileShare.Read, 16384)) // profiler time: 13937
using (Stream s = new FileStream(pclFile, FileMode.Open, FileAccess.Read, FileShare.Read, 32768)) // profiler time: 13776
using (Stream s = new FileStream(pclFile, FileMode.Open, FileAccess.Read, FileShare.Read, 65536)) // profiler time: 13833
using (Stream s = new FileStream(pclFile, FileMode.Open, FileAccess.Read, FileShare.Read, 131072)) // profiler time: 13857
using (Stream s = new FileStream(pclFile, FileMode.Open, FileAccess.Read, FileShare.Read, 1000000)) // profiler time: 13746
using (Stream s = new FileStream(pclFile, FileMode.Open, FileAccess.Read, FileShare.Read, 2000000)) // profiler time: 13814
using (Stream s = new FileStream(pclFile, FileMode.Open, FileAccess.Read, FileShare.Read, 5000000)) // profiler time: 13736
但这没什么用。
我认为更大的缓冲区需要更少的磁盘访问权限,而BinaryReader.Read(),.PeekChar()可以在磁盘上获得优势。但是那里没有运气
下一个想法是使用MemoryMappedFile并从中创建流:
long length = new FileInfo(path).Length;
using (var mmf = MemoryMappedFile.CreateFromFile(pclFile, FileMode.Open, "pclfile", length))
{
using (var viewStream = mmf.CreateViewStream(0, length, MemoryMappedFileAccess.Read))
{
// give this stream to the BinaryReader
}
}
虽然有一点改进,但仍然不太好(比如说现在评测时的时间是19,8秒,而不是20秒)
在此之后,我相信问题在于二进制阅读器。
现在最好的选择是什么?
我基本上需要二进制读取器中的.Read()
,.ReadBytes()
,ReadByte()
,PeekChar()
,位置和长度
我使用的是.NETFramework4.6.2
更新:
使用FileStream(File.OpenRead())-BinaryReader:
使用MemoryMappedViewStream-BinaryReader:
尽管需要对逻辑进行大量修改,但值得一看(设计时考虑了.NET Core,但也适用于Framework 4.6+)——它旨在解决以下问题:您希望以块形式读取数据以提高性能,但无法方便地在逻辑中以块形式处理数据(包括前瞻性要求)。使用缓冲区、读入内存或使用前面提到的System.IO.Pipelines。在IO上逐字节读取将非常昂贵。将整个文件读入内存流。然后将位置设置为零。读入BinaryReader并使用当前代码。我怀疑缓慢的时间是以块的形式从文件中读取。是文件检查的结束导致此死亡。使用 long len=br.BaseStream.Length;
在开始解析之前。使用if(br.BaseStream.Position>=len)退出break;
无需再进行操作系统调用来检查长度是否更改,在我的计算机上快了x25。@HansPassant BufferedStream和修改FileStream buffer没有起作用,仍然不知道原因。我已使用mmf.CreateViewStream()重试出于某种原因,多次重建它现在基准测试的效果要好得多(比方说5到7秒,而不是20秒)。memorymappedviewstream可以使用吗?例如,关于内存、服务器与桌面、虚拟环境……(以前从未在生产中使用过它)。如果您试图打开一个比可用(免费)RAM大的文件,MMF是如何工作的?虽然需要对逻辑进行大量修改,但值得一看(设计时考虑了.NET Core,但也适用于Framework 4.6+)--它旨在解决以下问题:您希望以块的形式读取数据以获得性能,但无法在逻辑中方便地以块的形式处理数据(包括前瞻性要求)。使用缓冲区、读入内存或使用前面提到的System.IO.Pipelines。在IO上逐字节读取将非常昂贵。将整个文件读入内存流。然后将位置设置为零。读入BinaryReader并使用当前代码。我怀疑缓慢的时间是以块的形式从文件中读取。是文件检查的结束导致此死亡。使用 long len=br.BaseStream.Length;
在开始解析之前。使用if(br.BaseStream.Position>=len)退出break;
无需再进行操作系统调用来检查长度是否更改,在我的计算机上快了x25。@HansPassant BufferedStream和修改FileStream buffer没有起作用,仍然不知道原因。我已使用mmf.CreateViewStream()重试出于某种原因,多次重建它现在基准测试的效果要好得多(比方说5到7秒,而不是20秒)。memorymappedviewstream可以使用吗?例如,关于内存、服务器与桌面、虚拟环境……(以前从未在生产中使用过它)。如果您试图打开一个大于可用内存的文件,MMF是如何工作的?