4

在编写一个简单的库来解析游戏的数据文件时,我注意到将整个数据文件读入内存并从那里解析明显更快(高达 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 本机CreateFileMappingMapViewOfFile读取文件,而且速度非常快。是否是从托管到非托管的不断切换以及导致这种情况的相关编组?

我还尝试MemoryMappedFile了 .NET 4 中的 s;速度增益只有大约一秒。

4

3 回答 3

3

我做错了什么,还是可以预料到的?

不,没有错。这完全是意料之中的。访问磁盘比访问内存慢一个数量级是非常合理的。


更新:

对文件的单次读取然后进行处理比在读取时进行处理更快也是预期的。

在内存中进行大型 IO 操作和处理将比从磁盘获取位、处理它、再次调用磁盘(等待 IO 完成)、处理该位等更快...

于 2012-05-10T13:03:21.913 回答
2

我做错了什么,还是可以预料到的?

与 RAM 相比,硬盘具有巨大的访问时间。顺序读取非常快,但是一旦磁头必须移动(因为数据是碎片化的),获取下一位数据需要很多毫秒,在此期间您的应用程序处于空闲状态。

有什么办法至少可以使经济放缓不那么明显?

购买固态硬盘。

您还可以查看.NET的内存映射文件

MemoryMappedFile.CreateFromFile().


至于您的编辑:我会选择@Oded 并说事先阅读文件会增加处罚。但是,这不应该导致首先读取整个文件的方法比“process-as-you-read”慢七倍。

于 2012-05-10T13:04:57.683 回答
0

我决定做一些基准测试,比较在 C++ 和 C# 中读取文件的各种方式。首先我创建了一个 256mb 的文件。在 c++ 基准测试中,缓冲意味着我首先将整个文件复制到缓冲区,然后从缓冲区读取数据。所有基准测试都直接或间接地按字节顺序读取文件并计算校验和。所有时间都是从我打开文件到我完全完成并关闭文件的那一刻开始计算的。所有基准测试都运行多次以保持一致的操作系统文件缓存。

C++
无缓冲内存映射文件:300ms
缓冲内存映射文件:500ms
无缓冲 fread:23,000ms
缓冲 fread: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,然后直接从数组中访问数据,而不使用流。

于 2012-05-10T14:42:21.990 回答