从磁盘解析大型数据文件比在内存中解析要慢得多
本文关键字:内存 大型 磁盘 数据 文件 | 更新日期: 2023-09-27 18:33:50
在编写一个简单的库来解析游戏的数据文件时,我注意到将整个数据文件读入内存并从那里解析的速度要快得多(高达 15 倍,106 秒 v 7 秒(。
解析通常是顺序的,但会不时地进行查找,以读取存储在文件中其他位置的一些数据,这些数据由偏移量链接。
我意识到从内存解析肯定会更快,但如果差异如此显着,那就出了问题。我写了一些代码来模拟这一点:
public static void Main(string[] args)
{
Stopwatch n = new Stopwatch();
n.Start();
byte[] b = File.ReadAllBytes(@"D:'Path'To'Large'File");
using (MemoryStream s = new MemoryStream(b, false))
RandomRead(s);
n.Stop();
Console.WriteLine("Memory read done in {0}.", n.Elapsed);
b = null;
n.Reset();
n.Start();
using (FileStream s = File.Open(@"D:'Path'To'Large'File", FileMode.Open))
RandomRead(s);
n.Stop();
Console.WriteLine("File read done in {0}.", n.Elapsed);
Console.ReadLine();
}
private static void RandomRead(Stream s)
{
// simulate a mostly sequential, but sometimes random, read
using (BinaryReader br = new BinaryReader(s)) {
long l = s.Length;
Random r = new Random();
int c = 0;
while (l > 0) {
l -= br.ReadBytes(r.Next(1, 5)).Length;
if (c++ <= r.Next(10, 15)) continue;
// simulate seeking
long o = s.Position;
s.Position = r.Next(0, (int)s.Length);
l -= br.ReadBytes(r.Next(1, 5)).Length;
s.Position = o;
c = 0;
}
}
}
我使用游戏的一个数据文件作为输入。该文件大约是102 MB,它产生了这个结果(Memory read done in 00:00:03.3092618. File read done in 00:00:32.6495245.
(,它的内存读取速度比文件快约11倍。
内存读取是在文件读取之前完成的,以尝试通过文件缓存提高其速度。它仍然慢得多。
我尝试过增加或减少FileStream
的缓冲区大小;没有什么能产生明显更好的结果,增加或减少太多只会降低速度。
是我做错了什么,还是意料之中?有没有办法至少让经济放缓不那么重要?
为什么一次读取整个文件然后解析它比同时读取和解析要快得多?
我实际上已经比较了用C++编写的类似库,它使用Windows本机CreateFileMapping
和MapViewOfFile
来读取文件,而且速度非常快。是否是从托管到非托管的不断切换以及所涉及的封送处理导致了这种情况?
我也尝试了 .NET 4 中存在的MemoryMappedFile
;速度提升只有大约一秒。
是我做错了什么,还是意料之中?
不,没有错。这完全是意料之中的。访问磁盘比访问内存慢一个数量级是合理的。
更新:
单次读取文件后进行处理比读取时处理更快。
在内存中执行大型 IO 操作和处理将比从磁盘获取位、处理它、再次调用磁盘(等待 IO 完成(、处理该位等更快......
是我做错了什么,还是意料之中?
与RAM相比,硬盘具有巨大的访问时间。顺序读取非常快,但是一旦磁头必须移动(因为数据是碎片化的(,就需要很多毫秒才能获得下一位数据,在此期间您的应用程序处于空闲状态。
有没有办法至少让经济放缓不那么重要?
购买固态硬盘。
还可以查看 .NET 的内存映射文件:
MemoryMappedFile.CreateFromFile()
.
至于你的编辑:我会和@Oded一起说,事先阅读文件会增加惩罚。但是,这不应导致首先读取整个文件的方法比"读取时处理"慢七倍。
我决定做一些基准测试,比较在C++和C#中读取文件的各种方法。首先,我创建了一个 256mb 的文件。在 c++ 基准测试中,buffered 意味着我首先将整个文件复制到缓冲区,然后从缓冲区读取数据。所有基准测试都直接或间接地逐字节按顺序读取文件并计算校验和。所有时间都是从我打开文件的那一刻开始测量的,直到我完全完成并关闭文件。所有基准测试都运行了多次,以保持一致的操作系统文件缓存。
C++
无缓冲内存映射文件:300ms
缓冲内存映射文件:500ms
无缓冲胎:23,000ms
缓冲胎面:500ms
无缓冲中频流:26,000ms
缓冲 ifstream:700ms
C#
内存映射文件:112,000ms
文件流:2,800ms
内存流:2,300ms
全部读取字节数:600ms
根据需要解释数据。C# 的内存映射文件甚至比最坏情况的 C++ 代码慢,而 C++ 的内存映射文件是最快的。C#的ReadAllBytes相当快,只有C++的内存映射文件的两倍。因此,如果您想要获得最佳性能,我建议您使用 ReadAllBytes,然后直接从数组访问数据,而无需使用流。