我正在使用以下代码从一个巨大的文件(> 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时有多大的相关性。