.NET 中更快(不安全)的 BinaryReader

Posted

技术标签:

【中文标题】.NET 中更快(不安全)的 BinaryReader【英文标题】:Faster (unsafe) BinaryReader in .NET 【发布时间】:2010-11-17 07:49:47 【问题描述】:

我遇到了一个情况,我有一个非常大的文件,我需要从中读取二进制数据。

因此,我意识到 .NET 中的默认 BinaryReader 实现非常慢。在用.NET Reflector 看它时,我遇到了这个:

public virtual int ReadInt32()

    if (this.m_isMemoryStream)
    
        MemoryStream stream = this.m_stream as MemoryStream;
        return stream.InternalReadInt32();
    
    this.FillBuffer(4);
    return (((this.m_buffer[0] | (this.m_buffer[1] << 8)) | (this.m_buffer[2] << 0x10)) | (this.m_buffer[3] << 0x18));

想想自从 32 位 CPU 发明以来计算机是如何设计为使用 32 位值的,这让我觉得效率极低。

所以我用这样的代码创建了自己的(不安全的)FastBinaryReader 类:

public unsafe class FastBinaryReader :IDisposable

    private static byte[] buffer = new byte[50];
    //private Stream baseStream;

    public Stream BaseStream  get; private set; 
    public FastBinaryReader(Stream input)
    
        BaseStream = input;
    


    public int ReadInt32()
    
        BaseStream.Read(buffer, 0, 4);

        fixed (byte* numRef = &(buffer[0]))
        
            return *(((int*)numRef));
        
    
...

这要快得多 - 我设法将读取 500 MB 文件的时间缩短了 5-7 秒,但总体而言仍然很慢(最初是 29 秒,现在使用我的 FastBinaryReader 大约需要 22 秒) .

我仍然有点困惑,为什么要花这么长时间才能读取这么一个相对较小的文件。如果我将文件从一个磁盘复制到另一个磁盘只需几秒钟,因此磁盘吞吐量不是问题。

我进一步内联了 ReadInt32 等调用,最后得到了以下代码:

using (var br = new FastBinaryReader(new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan)))

  while (br.BaseStream.Position < br.BaseStream.Length)
  
      var doc = DocumentData.Deserialize(br);
      docData[doc.InternalId] = doc;
  


   public static DocumentData Deserialize(FastBinaryReader reader)
   
       byte[] buffer = new byte[4 + 4 + 8 + 4 + 4 + 1 + 4];
       reader.BaseStream.Read(buffer, 0, buffer.Length);

       DocumentData data = new DocumentData();
       fixed (byte* numRef = &(buffer[0]))
       
           data.InternalId = *((int*)&(numRef[0]));
           data.b = *((int*)&(numRef[4]));
           data.c = *((long*)&(numRef[8]));
           data.d = *((float*)&(numRef[16]));
           data.e = *((float*)&(numRef[20]));
           data.f = numRef[24];
           data.g = *((int*)&(numRef[25]));
       
       return data;
   

关于如何使这更快的任何进一步的想法?我在想也许我可以使用编组将整个文件直接映射到某个自定义结构之上的内存中,因为数据是线性、固定大小和顺序的。

已解决:我得出的结论是 FileStream 的缓冲/BufferedStream 存在缺陷。请在下面查看接受的答案和我自己的答案(带有解决方案)。

【问题讨论】:

可能有帮助:***.com/questions/19558435/… 【参考方案1】:

我在使用 BinaryReader/FileStream 时遇到了类似的性能问题,在进行分析后,我发现问题不在于 FileStream 缓冲,而在于这一行:

while (br.BaseStream.Position < br.BaseStream.Length) 

具体来说,FileStream 上的属性 br.BaseStream.Length 会进行(相对)慢速系统调用以获取每个循环的文件大小。把代码改成这样后:

long length = br.BaseStream.Length;
while (br.BaseStream.Position < length) 

并为FileStream 使用适当的缓冲区大小,我获得了与MemoryStream 示例相似的性能。

【讨论】:

【参考方案2】:

有趣的是,将整个文件读入缓冲区并在内存中遍历它会产生巨大的差异。这是以内存为代价的,但我们有很多。

这让我觉得 FileStream(或 BufferedStream 的)缓冲区实现存在缺陷,因为无论我尝试什么大小的缓冲区,性能仍然很差。

  using (var br = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan))
  
      byte[] buffer = new byte[br.Length];
      br.Read(buffer, 0, buffer.Length);
      using (var memoryStream = new MemoryStream(buffer))
      
          while (memoryStream.Position < memoryStream.Length)
          
              var doc = DocumentData.Deserialize(memoryStream);
              docData[doc.InternalId] = doc;
          
      
  

现在从 22 秒减少到 2-5 秒(取决于我猜的磁盘缓存)。现在已经足够了。

【讨论】:

所以我的回答并没有那么有缺陷 ;^) 谢谢。但实际上.NET 的缓冲区实现存在问题,因为我尝试了与文件一样大的缓冲区大小(应该相当于中间的 MemoryStream),但在性能方面仍然很糟糕。理论上你的建议应该是多余的,但在实践中 - 大奖。 你可以说 var buffer = File.ReadAllBytes(cacheFilePath);节省一些代码,速度更快【参考方案3】:

当您进行文件复制时,会读取大量数据并将其写入磁盘。

您一次读取整个文件四个字节。这势必会慢一些。即使流实现足够智能以进行缓冲,您仍然有至少 500 MB/4 = 131072000 次 API 调用。

直接读取一大块数据,然后依次遍历,重复直到文件处理完,不是更明智吗?

【讨论】:

FileStream 构造函数中有一个参数指定缓冲区大小,所以读取确实是分块完成的。我尝试了缓冲区大小的各种值,但没有重大改进。超大的缓冲区大小实际上会损害我的测量性能。 您仍在对“ReadInt32”进行大量调用。自己从一段连续的记忆中得到它会快得多。 请重新阅读问题,我在实际实现中没有使用ReadInt32,每个对象只有1次读取,并且所有的转换都是内联的,见最后两块代码。跨度> 对...对此感到抱歉。那么我想大量的小内存分配可能是问题所在。 我会将您的问题视为已接受的答案,因为您建议从文件中读取大量数据。如果实际 FileStream 的缓冲实现没有缺陷,那将是多余的,但显然它是。【参考方案4】:

一个警告;您可能需要仔细检查您的 CPU's endianness... 假设 little-endian 不是 相当 安全(想想:安腾等)。

您可能还想看看BufferedStream 是否有任何不同(我不确定它会不会)。

【讨论】:

是的,我知道字节序问题,但这是一个专有应用程序,我可以完全控制部署。关于 BufferedStream,据我了解 FileStream 已经被缓冲,所以它只会添加一个不必要的中间缓冲区。 P.S.:我也在这个项目中使用你的 protobuf 库,非常感谢:) 我刚刚用包装的 BufferedStream 做了一个新的测量,和预期的一样,没有区别。

以上是关于.NET 中更快(不安全)的 BinaryReader的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET7大内置对象以及对应功能?

一个比NPM更快更安全可靠的JavaScript包管理工具——Yarn

.NET 中有啥比 SqlDataReader 更快的吗?

Windows 上的 MySQL 命名管道——更快的最佳实践,还是坏主意?

在 ASP.NET Core 响应后做一些工作

为啥在 VB.NET 中使用 DeviceIoControl 进行文件枚举比在 C++ 中更快?