and*_*ecu 24 c# performance pointers unsafe binaryreader
我遇到了一个情况,我有一个非常大的文件,我需要从中读取二进制数据.
因此,我意识到.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));
}
Run Code Online (Sandbox Code Playgroud)
这让我觉得非常低效,想到自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));
}
}
...
}
Run Code Online (Sandbox Code Playgroud)
哪个更快 - 我设法减少了读取500 MB文件所花费的时间5-7秒,但总体上仍然很慢(最初为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;
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
关于如何使这更快的任何进一步的想法?我想也许我可以使用编组将整个文件直接映射到一些自定义结构的内存中,因为数据是线性的,固定的大小和顺序.
解决:我得出结论,FileStream的缓冲/ BufferedStream是有缺陷的.请参阅下面接受的答案和我自己的答案(使用解决方案).
小智 18
我遇到了与BinaryReader/FileStream类似的性能问题,在进行性能分析后,我发现问题不是FileStream缓冲,而是使用此行:
while (br.BaseStream.Position < br.BaseStream.Length) {
Run Code Online (Sandbox Code Playgroud)
具体来说,财产br.BaseStream.Length上FileStream作出的(相对)慢速系统调用来获取在每次循环的文件大小.将代码更改为此后:
long length = br.BaseStream.Length;
while (br.BaseStream.Position < length) {
Run Code Online (Sandbox Code Playgroud)
并且使用适当的缓冲区大小FileStream,我实现了与MemoryStream示例类似的性能.
Toa*_*oad 10
执行文件复制时,会读取大块数据并将其写入磁盘.
您正在一次读取整个文件四个字节.这肯定会慢一些.即使流实现足够智能缓冲,您仍然至少有500 MB/4 = 131072000 API调用.
是不是更明智地只读取大量数据,然后按顺序遍历它,并重复直到文件被处理?
and*_*ecu 10
有趣的是,将整个文件读入缓冲区并在内存中进行处理会产生巨大的差异.这是以记忆为代价的,但我们有很多.
这让我觉得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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在从22下降到2-5秒(取决于我猜的磁盘缓存).现在已经足够好了.