从磁盘解析大数据文件比在内存中解析要慢得多?

时间:2012-05-10 13:00:27

标签: c# .net .net-3.5

在编写一个简单的库来解析游戏的数据文件时,我注意到将整个数据文件读入内存并从中解析速度明显更快(最多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本机CreateFileMappingMapViewOfFile来读取文件,而且速度非常快。可能是从托管到非托管的不断切换以及导致此问题的编组操作吗?

我也试过.NET 4中的MemoryMappedFile;速度增加只有一秒左右。

3 个答案:

答案 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,然后直接从阵列访问数据而不使用流。