在.NET更快(不安全)BinaryReader在(Faster (unsafe) BinaryRe

2019-07-31 13:20发布

我碰到一个情况我有一个相当大的文件,我需要读取二进制数据。

因此,我意识到,在.NET中的默认BinaryReader在实现是相当缓慢的。 在与看着它.net反射我碰到这样的:

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位值来工作,因为32位CPU的发明想法。

所以我做了我自己的(不安全)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));
        }
    }
...
}

这是更快-我设法剃掉5-7秒掉花了读取500MB的文件时,但它仍然是相当缓慢的整体(29秒开始,并〜22日秒钟现在我FastBinaryReader )。

它还是一种令我感到困惑,为什么它仍然需要很长时间来读取这样一个相对较小的文件。 如果我从一个磁盘文件复制到另一个只需要几秒钟,所以磁盘吞吐量是不是一个问题。

我还内联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是有缺陷的。 请参阅下面的接受的答案和我自己的答案(与解决方案)。

Answer 1:

当你做一个filecopy,大量的数据被读取并写入磁盘。

您正在阅读的整个文件的4个字节的时间。 这势必要慢一些。 即使流实现是足够聪明的缓冲,你仍然有至少500 MB / 4 = 131072000 API调用。

是不是更明智的,刚读一大块数据,然后通过它顺序,并重复,直到该文件已被受理?



Answer 2:

我跑进与BinaryReader在/的FileStream类似的性能问题,并分析后,我发现,问题不在于FileStream缓冲,而是与这一行:

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

具体而言,物业br.BaseStream.LengthFileStream使一个(相对)慢速系统调用来获取在每次循环的文件大小。 代码更改为此后:

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

和使用用于适当的缓冲大小FileStream ,我取得类似性能的MemoryStream例子。



Answer 3:

有趣的是,在阅读整个文件到缓冲区,并通过它在内存中会取得了巨大的差异。 这是在内存中的成本,但我们有的是。

这让我觉得,因此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;
          }
      }
  }

下到2-5秒(取决于磁盘缓存我猜)现在从22这是不够好现在。



Answer 4:

一个需要注意的; 你可能要仔细检查你的CPU的字节序 ...假设小尾数是不太安全的(认为:安腾等)。

你可能也想看看BufferedStream有什么差别(我不知道它会)。



文章来源: Faster (unsafe) BinaryReader in .NET