Binaryreader从文件流中读取,文件流以块形式加载



我正在使用以下代码从一个巨大的文件(> 10 GB)读取值:

FileStream fs = new FileStream(fileName, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
int count = br.ReadInt32();
List<long> numbers = new List<long>(count);
for (int i = count; i > 0; i--)
{
    numbers.Add(br.ReadInt64());
}

不幸的是,我的SSD读取速度停留在几MB/s。我猜限制是SSD的IOPS,所以最好从文件中读取块。

<标题>

FileStream在我的代码真的读只有8字节从文件每次BinaryReader调用ReadInt64()?

如果是这样,是否有一种透明的方式为BinaryReader提供一个从文件中读取大块的流来加快过程?

<标题>测试代码下面是一个创建测试文件并测量读取性能的最小示例。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace TestWriteRead
{
    class Program
    {
        static void Main(string[] args)
        {
            System.IO.File.Delete("test");
            CreateTestFile("test", 1000000000);
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            IEnumerable<long> test = Read("test");
            stopwatch.Stop();
            Console.WriteLine("File loaded within " + stopwatch.ElapsedMilliseconds + "ms");
        }
        private static void CreateTestFile(string filename, int count)
        {
            FileStream fs = new FileStream(filename, FileMode.CreateNew);
            BinaryWriter bw = new BinaryWriter(fs);
            bw.Write(count);
            for (int i = 0; i < count; i++)
            {
                long value = i;
                bw.Write(value);
            }
            fs.Close();
        }
        private static IEnumerable<long> Read(string filename)
        {
            FileStream fs = new FileStream(filename, FileMode.Open);
            BinaryReader br = new BinaryReader(fs);
            int count = br.ReadInt32();
            List<long> values = new List<long>(count);
            for (int i = 0; i < count; i++)
            {
                long value = br.ReadInt64();
                values.Add(value);
            }
            fs.Close();
            return values;
        }
    }
}

您应该将流配置为使用SequentialScan来指示您将从头到尾读取流。它应该能显著提高速度。

表示从开始顺序访问结束。系统可以将此作为优化文件缓存的提示。如果应用程序移动文件指针进行随机访问,最优缓存可能不会发生;但是,仍然保证正确的操作。

using (
    var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 8192,
        FileOptions.SequentialScan))
{
    var br = new BinaryReader(fs);
    var count = br.ReadInt32();
    var numbers = new List<long>();
    for (int i = count; i > 0; i--)
    {
        numbers.Add(br.ReadInt64());
    }
}

尝试读取块:

using (
var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 8192,
FileOptions.SequentialScan))
{
    var br = new BinaryReader(fs);
    var numbersLeft = (int)br.ReadInt64();
    byte[] buffer = new byte[8192];
    var bufferOffset = 0;
    var bytesLeftToReceive = sizeof(long) * numbersLeft;
    var numbers = new List<long>();
    while (true)
    {
        // Do not read more then possible
        var bytesToRead = Math.Min(bytesLeftToReceive, buffer.Length - bufferOffset);
        if (bytesToRead == 0)
            break;
        var bytesRead = fs.Read(buffer, bufferOffset, bytesToRead);
        if (bytesRead == 0)
            break; //TODO: Continue to read if file is not ready?
        //move forward in read counter
        bytesLeftToReceive -= bytesRead;
        bytesRead += bufferOffset; //include bytes from previous read.
        //decide how many complete numbers we got
        var numbersToCrunch = bytesRead / sizeof(long);
        //crunch them
        for (int i = 0; i < numbersToCrunch; i++)
        {
            numbers.Add(BitConverter.ToInt64(buffer, i * sizeof(long)));
        }
        // move the last incomplete number to the beginning of the buffer.
        var remainder = bytesRead % sizeof(long);
        Buffer.BlockCopy(buffer, bytesRead - remainder, buffer, 0, remainder);
        bufferOffset = remainder;
    }
}

响应注释更新:

我可以知道手动阅读比其他阅读快的原因吗?

我不知道BinaryReader实际上是如何实现的。这只是假设。

实际从磁盘读取并不是昂贵的部分。昂贵的部分是将读取器臂移动到磁盘上的正确位置。

由于您的应用程序不是唯一一个从硬盘驱动器读取的程序,因此每次应用程序请求读取时,磁盘都必须重新定位自己。

因此,如果BinaryReader只读取请求的int,它必须在磁盘上等待每次读取(如果其他应用程序在中间进行读取)。

当我直接读取更大的缓冲区时(这更快),我可以处理更多的整数,而不必在读取之间等待磁盘。

缓存当然会加快一些速度,这就是为什么它"只是"快了三倍。

你可以使用BufferedStream来增加读缓冲区的大小。

理论上内存映射文件在这里应该有帮助。您可以使用几个非常大的块将它加载到内存中。但我不确定这在使用ssd时有多大的相关性。

相关内容

  • 没有找到相关文章

最新更新