使用FileStream.WriteFile时出现奇怪的行为

9

我正在开发一个程序,对巨大文件(最高达64 GB)进行大量随机读写操作。这些文件是特定结构的,为了访问它们,我创建了一个框架;一段时间后,我试图测试其性能,发现在预分配的文件上,顺序写入操作太慢而无法接受。 经过多次测试,我没有使用我的框架(仅使用FileStream方法)复制了该行为;以下是代码部分(使用我的硬件)可复制该问题:

FileStream fs = new FileStream("test1.vhd", FileMode.Open);
byte[] buffer = new byte[256 * 1024];
Random rand = new Random();
rand.NextBytes(buffer);
DateTime start, end;
double ellapsed = 0.0;
long startPos, endPos;

BinaryReader br = new BinaryReader(fs);
br.ReadUInt32();
br.ReadUInt32();
for (int i = 0; i < 65536; i++)
    br.ReadUInt16();

br = null;

startPos = 0;   // 0
endPos = 4294967296;    // 4GB
for (long index = startPos; index < endPos; index += buffer.Length)
{
    start = DateTime.Now;
    fs.Write(buffer, 0, buffer.Length);
    end = DateTime.Now;
    ellapsed += (end - start).TotalMilliseconds;
}

很不幸,这个问题似乎是不可预测的,所以有时它“工作”,有时则不然。但是,使用进程监视器,我捕获了以下事件:
操作 结果 详细信息 WriteFile 成功 偏移量:1.905.655.816,长度:262.144 WriteFile 成功 偏移量:1.905.917.960,长度:262.144 WriteFile 成功 偏移量:1.906.180.104,长度:262.144 WriteFile 成功 偏移量:1.906.442.248,长度:262.144 WriteFile 成功 偏移量:1.906.704.392,长度:262.144 WriteFile 成功 偏移量:1.906.966.536,长度:262.144 ReadFile 成功 偏移量:1.907.228.672,长度:32.768,I/O标志:非缓存,分页I/O,同步分页I/O,优先级:普通 WriteFile 成功 偏移量:1.907.228.680,长度:262.144 ReadFile 成功 偏移量:1.907.355.648,长度:32.768,I/O标志:非缓存,分页I/O,同步分页I/O,优先级:普通 ReadFile 成功 偏移量:1.907.490.816,长度:32.768,I/O标志:非缓存,分页I/O,同步分页I/O,优先级:普通 WriteFile 成功 偏移量:1.907.490.824,长度:262.144 ReadFile 成功 偏移量:1.907.617.792,长度:32.768,I/O标志:非缓存,分页I/O,同步分页I/O,优先级:普通 ReadFile 成功 偏移量:1.907.752.960,长度:32.768,I/O标志:非缓存,分页I/O,同步分页I/O,优先级:普通 WriteFile 成功 偏移量:1.907.752.968,长度:262.144
也就是说,在覆盖了将近2GB后,FileStream.Write开始在每次WriteFile之后调用ReadFile,并且这个问题会持续到进程结束;此外,问题开始的偏移量似乎是随机的。我已经逐步调试了FileStream.Write方法,并验证了实际上是WriteFile(Win32 API)在内部调用了ReadFile。
最后注意一点:我不认为这是文件碎片问题:我已经使用contig自己对文件进行了碎片整理!

1
考虑切换到内存映射文件。 - gor
你的意思是我应该从Win32 API创建引用还是使用.NET4?在第一种情况下,最好使用C / C ++创建整个框架(我真的在考虑这个可能性!); 在后者中,我还应该升级到VS2010或使用SharpDevelop:我更喜欢使用我已经拥有的工具! - Atropo
我已经在构造函数中添加了FileOptions.WriteThrough:没有任何更改! - Atropo
对的,另外2.1 GB被文件系统缓存使用了。这是你填满的。ReadFile调用是针对分页文件的。 - Hans Passant
另一个例子:使用Cacheset(来自Sysinternals),我可以确认文件系统高速缓存的最大大小为102 MB,没有内存不足! - Atropo
显示剩余4条评论
2个回答

1

我认为这与FileStream.Write / Read和2GB限制有关。您是否在32位进程中运行此操作?我找不到任何具体的文档,但这里有一个MSDN论坛问题听起来很相似。您可以尝试在64位进程中运行此操作。

然而,我同意使用内存映射文件可能是更好的方法。


我在一个Win7 64位系统中!但我不认为这是FileStream.Write的问题:我已经对其进行了调试(在mscorlib反编译后)! - Atropo
这是非常有可能的。据我了解,.NET 仍然限制于 32 位进程或 2 GB 内存限制。但你并没有分配超过 2GB 的内存,所以我怀疑那不是问题所在。 - JP Richardson
.NET 应用程序的目标是“Any CPU”还是 x86? - Mike Ohlsen
.Net的FileStream.Write/Read可以处理大于2GB的文件。 - user7116
@sixlettervariables:是的,你说得对:它可以!否则,我怎么能创建一个4 GB的文件呢? - Atropo
@Mike Ohlsen,我想为内存映射文件建议点赞,但是你的回答中存在2GB / 32位问题。 - user7116

1

我从MSDN上找到了这个。它可能与此相关吗?听起来每个文件都有一个全局共享指针。

当FileStream对象没有独占其句柄时,另一个线程可以同时访问文件句柄并更改与文件句柄相关联的操作系统文件指针的位置。在这种情况下,FileStream对象中的缓存位置和缓冲区中的缓存数据可能会受到损害。FileStream对象定期对访问缓存缓冲区的方法执行检查,以确保操作系统的句柄位置与FileStream对象使用的缓存位置相同。

http://msdn.microsoft.com/en-us/library/system.io.filestream.aspx


根据文档,似乎只需要使用FileOptions.WriteThrough来禁用FileStream.Write和磁盘之间的所有缓存即可;但在测试期间我仍然观察到了ReadFile的存在。 - Atropo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接