【问题标题】:Binaryreader read from Filestream which loads in chunksBinaryreader 从分块加载的 Filestream 读取
【发布时间】:2016-01-17 18:07:28
【问题描述】:

我正在使用以下代码从一个大文件 (> 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,所以从文件中分块读取可能会更好。

问题

每次 BinaryReader 调用 ReadInt64() 时,我的代码中的 FileStream 是否真的只从文件中读取 8 个字节?

如果是这样,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;
        }
    }
}

【问题讨论】:

  • 刚刚看到我最初的评论有缺陷。读取命令的数量可能确实是那里的原因(否则通常我会认为缓存或内存大小是主要原因)。 Veras 链接问题应该为您的问题提供一个很好的观点。如果读取这些块没有帮助,那么瓶颈在其他地方,您还应该检查内存使用情况和 cpu 使用情况并将其放入您的问题中。
  • 你可以使用 File.ReadAllBytes 吗?然后从字节中制作内存流,然后使用 BitConverter 读取数据?它比寻找更快,
  • 很遗憾我不能,因为 File:ReadAllBytes 只支持 2 GB 文件。
  • @user2033412:您在count 变量中读取的值是多少?

标签: c# performance io filestream binaryreader


【解决方案1】:

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

【讨论】:

  • 听起来不错,不幸的是它没有改变任何东西。我尝试了从 4k 到 128k 的不同缓冲区大小——完全没有区别 :-(
  • @user2033412 由于您对原始问题的评论是 2GB 文件是不够的,我怀疑 128k 缓冲区基本上可以忽略不计。在拒绝缓冲流的想法之前尝试使用 50-100MB。也许更大。
  • 我添加了示例代码来创建文件并测量读取性能。
【解决方案2】:

您应该将流配置为使用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,它必须在磁盘上等待每次读取(如果其他应用程序在中间进行读取)。

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

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

(未来的读者:如果上面有什么不正确的地方,请纠正我)。

【讨论】:

  • 听起来不错,不幸的是这也没有改变任何东西。我还尝试了从 4k 到 128k 的不同缓冲区大小,但没有运气:-(
  • 我添加了示例代码来创建文件并测量读取性能。
  • 我的第二个示例从文件中读取块而不是使用二进制阅读器。
  • 天啊!你的代码比我的原始代码快三倍!太棒了!
  • 欢迎您 :) 您也可以加快速度。例如,通过从列表开始分配正确的大小:new List&lt;long&gt;(fs.Length / sizeof(long)); 并尝试缓冲区大小(流和字节缓冲区应该具有相同的大小)
【解决方案3】:

理论上memory mapped files 应该在这里有所帮助。您可以使用几个非常大的块将其加载到内存中。不知道这在使用 SSD 时有多少相关性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-13
    • 1970-01-01
    • 2021-08-08
    • 2012-03-08
    • 2012-03-16
    • 1970-01-01
    • 1970-01-01
    • 2017-08-23
    相关资源
    最近更新 更多