在.NET更快(不安全)BinaryReader在
-
11-09-2019 - |
题
我碰到一个情况我有一个相当大的文件,我需要读取二进制数据。
因此,我意识到,在.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秒开始,并〜现在我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是有缺陷的。请参阅下面的接受的答案和我自己的答案(与解决方案)。
解决方案
当你做filecopy,大量的数据被读出和写入磁盘。
您正在阅读的整个文件在时间四个字节。这势必要慢一些。即使流实现足够聪明来缓冲,仍具有至少500 MB / 4 = 131072000 API调用。
是不是更明智的,刚读一大块数据,然后通过它顺序,并重复,直到该文件已被处理?
其他提示
我跑进与BinaryReader在/的FileStream类似的性能问题,并分析后,我发现,问题不在于FileStream
缓冲,而是这一行:
while (br.BaseStream.Position < br.BaseStream.Length) {
具体地,在一个br.BaseStream.Length
属性FileStream
使得(相对)缓慢系统调用以获得在每次循环中的文件大小。代码改变为在此之后:
long length = br.BaseStream.Length;
while (br.BaseStream.Position < length) {
和使用适当的缓冲区大小FileStream
,我取得类似性能的MemoryStream
示例
有趣的是,读取整个文件到缓冲区,并通过它在存储器中去制成的巨大差异。这是在存储器的成本,但我们有大量的
这让我觉得,因此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这是不够好了。
一个警告;你可能要仔细检查自己的 CPU的字节序 ......假设小端不是的非常的安全(认为:安腾等)。
您可能也想看看是否BufferedStream
有什么差别(我不知道它会)。