【发布时间】:2018-11-16 08:51:34
【问题描述】:
假设我有多个进程写入大文件 (20gb+)。每个进程都在写入自己的文件,并假设该进程一次写入 x mb,然后进行一些处理并再次写入 x mb,等等。
发生的情况是,这种写入模式会导致文件严重碎片化,因为文件块是在磁盘上连续分配的。
当然,通过使用SetEndOfFile 在打开文件时“预分配”文件,然后在关闭文件之前设置正确的大小,可以轻松解决此问题。但是现在远程访问这些文件的应用程序能够解析这些正在进行的文件,显然在文件末尾看到零并且解析文件需要更长的时间。
我无法控制此阅读应用程序,因此无法对其进行优化以考虑最后为零。
另一个肮脏的解决办法是更频繁地运行碎片整理,运行 Systernal 的 contig 实用程序,甚至实现一个自定义的“碎片整理程序”,它将处理我的文件并将它们的块合并在一起。
另一个更激进的解决方案是实现一个微过滤器驱动程序,它会报告一个“假”文件大小。
但显然上面列出的两种解决方案都远非最佳。所以我想知道是否有办法向文件系统提供文件大小提示,以便它“保留”驱动器上的连续空间,但仍向应用程序报告正确的文件大小?
否则显然一次写入更大的块显然有助于碎片化,但仍然不能解决问题。
编辑:
由于SetEndOfFile 在我的案例中的用处似乎存在争议,我做了一个小测试:
LARGE_INTEGER size;
LARGE_INTEGER a;
char buf='A';
DWORD written=0;
DWORD tstart;
std::cout << "creating file\n";
tstart = GetTickCount();
HANDLE f = CreateFileA("e:\\test.dat", GENERIC_ALL, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
size.QuadPart = 100000000LL;
SetFilePointerEx(f, size, &a, FILE_BEGIN);
SetEndOfFile(f);
printf("file extended, elapsed: %d\n",GetTickCount()-tstart);
getchar();
printf("writing 'A' at the end\n");
tstart = GetTickCount();
SetFilePointer(f, -1, NULL, FILE_END);
WriteFile(f, &buf,1,&written,NULL);
printf("written: %d bytes, elapsed: %d\n",written,GetTickCount()-tstart);
当应用程序执行并在 SetEndOfFile 之后等待按键时,我检查了磁盘上的 NTFS 结构:
图像显示 NTFS 确实为我的文件分配了簇。然而,未命名的 DATA 属性将 StreamDataSize 指定为 0。
当按下回车键允许测试继续时(并等待相当长的时间,因为文件是在慢速 USB 记忆棒上创建的),StreamDataSize 字段已更新
由于我最后写了 1 个字节,NTFS 现在真的不得不将所有内容归零,所以SetEndOfFile 确实有助于解决我“烦恼”的问题。
非常感谢答案/cmets 还提供官方参考来支持正在提出的声明。
哦,在我的情况下,测试应用程序会输出这个:
creating file
file extended, elapsed: 0
writing 'A' at the end
written: 1 bytes, elapsed: 21735
为了完整起见,这里是一个示例,设置 FileAllocationInfo 时 DATA 属性的外观(请注意,我为此图片创建了一个新文件)
【问题讨论】:
-
我真的很好奇为什么我的问题遭到了反对,请反对者解释原因以便我改进我的问题?
-
SetEndOfFile 技巧无论如何都没有做任何事情,它只是更新目录条目,但实际上并没有分配任何集群。你自己看不到这一点是一个很好的暗示,表明你正在为一个不相关的问题而烦恼。
-
可以确认
SetEndOfFile()确实减少并且通常可以防止碎片。想知道当我们为我们的一种产品实施并行下载以对抗 RTT 时是否会有所帮助,并且确实如此。很好地发现了这一点。 @HansPassant 这就是预期的效果。如果它在阻塞操作中立即分配,我们将不必要地停止应用程序。它只是对文件系统 API 的一个提示,它恰好采用了正确的方式:“如果可以的话,保留这么多可用的完整空间来扩展文件”。
标签: windows ntfs hint refs pre-allocation