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

也许这个问题已经被问过很多次了,但仍然在为这个问题而挣扎

场景:C#库,其中包含一个解释器/解析器,该解释器/解析器打开文件并对其进行解释/解析,大量使用以下逻辑实现:

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是如何工作的?