在编写一个简单的库来解析游戏的数据文件时,我注意到将整个数据文件读入内存并从中解析速度明显更快(最多15x,106s v 7s)。
解析通常是连续的,但是会不时地进行搜索,以便读取存储在文件中其他地方的一些数据,并通过偏移进行链接。
我意识到从内存中解析肯定会更快,但如果差异如此显着,则会出现问题。我写了一些代码来模拟这个:
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
;速度增加只有一秒左右。
答案 0 :(得分:3)
有什么我做错了,还是这是预期的?
不,没有错。这完全预期。访问磁盘比访问内存慢一个数量级是合理的。
更新
对文件进行单次读取然后进行处理比处理期间的处理速度更快。
在内存中执行大型IO操作和处理比从磁盘中获取,处理它,再次调用磁盘(等待IO完成),处理该位等更快...
答案 1 :(得分:2)
有什么我做错了,还是这是预期的?
与RAM相比,硬盘具有巨大的访问时间。顺序读取速度非常快,但只要磁头必须移动(因为数据碎片化),需要花费很多毫秒来获取下一位数据,在此期间您的应用程序处于空闲状态。
有没有办法让减速不那么重要?
购买SSD。
您还可以查看Memory-Mapped Files for .NET:
MemoryMappedFile.CreateFromFile()
至于你的编辑:我会和@Oded一起说,事先阅读文件会增加一个惩罚。但是,这不应该导致首先读取整个文件的方法的速度是“process-as-read”的七倍。
答案 2 :(得分:0)
我决定做一些基准测试,比较用C ++和C#读取文件的各种方法。首先,我创建了一个256mb的文件。在c ++基准测试中,缓冲意味着我首先将整个文件复制到缓冲区,然后从缓冲区中读取数据。所有基准测试都是按字节顺序直接或间接读取文件并计算校验和。所有时间都是从我打开文件的那一刻开始测量,直到我完成并且文件关闭。所有基准测试都运行多次,以保持一致的OS文件缓存。
C ++
无缓冲内存映射文件:300ms
缓冲内存映射文件:500ms
无缓冲的fread:23,000ms
缓冲误差:500ms
无缓冲的ifstream:26,000ms
缓冲ifstream:700ms
C#
MemoryMappedFile:112,000ms
FileStream:2,800ms
MemoryStream:2,300ms
ReadAllBytes:600ms
根据需要解释数据。 C#的内存映射文件比最坏的c ++代码慢,而c ++的内存映射文件是最快的东西。 C#的ReadAllBytes速度非常快,只是c ++内存映射文件的两倍。因此,如果您希望获得最佳性能,我建议您使用ReadAllBytes,然后直接从阵列访问数据而不使用流。