Windows (ReFS​​,NTFS) 文件预分配提示

Windows (ReFS,NTFS) file preallocation hint

假设我有多个进程写入大文件 (20gb+)。每个进程都在写入自己的文件并假设该进程一次写入 x mb,然后进行一些处理并再次写入 x mb,等等。

这种写入模式会导致文件严重碎片化,因为文件块在磁盘上连续分配。

当然,通过在文件打开时使用 SetEndOfFile 到 "preallocate" 然后在关闭前设置正确的大小,可以很容易地解决此问题。但是现在远程访问这些文件的应用程序能够解析这些正在进行的文件,显然会在文件末尾看到零,并且需要更长的时间来解析文件。 我无法控制这个阅读应用程序,所以我无法优化它以考虑最后的零。

另一个肮脏的修复方法是 运行 更频繁地进行碎片整理,运行 Systernal 的 contig 实用程序甚至实现自定义 "defragmenter" 来处理我的文件并将它们的块合并在一起。

另一个更激进的解决方案是实现一个 minifilter 驱动程序,它会报告 "fake" 文件大小。

但显然上面列出的两种解决方案都远非最佳。所以我想知道是否有办法向文件系统提供文件大小提示,以便它 "reserves" 驱动器上的连续 space,但仍向应用程序报告正确的文件大小?

否则显然一次写入更大的块显然有助于碎片化,但仍然没有解决问题。

编辑:

由于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.

Systernals DiskView 还确认集群已分配

当按回车键允许测试继续时(并等待相当长的时间,因为文件是在慢速 USB 记忆棒上创建的),StreamDataSize 字段已更新

因为我在最后写了 1 个字节,NTFS 现在真的必须将所有内容清零,所以 SetEndOfFile 确实有助于解决我 "fretting" 的问题。

非常感谢 answers/comments 也提供官方参考来支持所提出的主张。

哦,在我的例子中测试应用程序输出这个:

creating file
file extended, elapsed: 0

writing 'A' at the end
written: 1 bytes, elapsed: 21735

同样为了完整起见,这里有一个示例,当设置 FileAllocationInfo 时,DATA 属性的样子(请注意,我为此图片创建了一个新文件)

Windows 文件系统维护两种 public 文件数据大小,在 FileStandardInformation:

中报告
  • AllocationSize - 文件的分配大小(以字节为单位),通常是扇区或簇大小的倍数。
  • EndOfFile - 文件的绝对文件结尾位置作为从文件开头的字节偏移量,必须小于或等于分配大小。

设置超过当前分配大小的文件结尾会隐式扩展分配。设置小于当前文件末尾的分配大小会隐式截断文件末尾。

从 Windows Vista 开始,我们可以通过 SetFileInformationByHandle: FileAllocationInfo 手动扩展分配大小而无需修改文件末尾。您可以使用 Sysinternals DiskView 来验证这是否为文件分配了簇。当文件关闭时,分配被截断到文件的当前末尾。

如果不介意直接使用NTAPI,也可以调用NtSetInformationFile: FileAllocationInformation. Or even set the allocation size at creation via NtCreateFile.


仅供参考,还有一个内部 ValidDataLength 大小,它必须小于或等于文件末尾。随着文件的增长,磁盘上的簇被延迟初始化。读取超出有效区域 returns 个零。超出有效区域的写入通过将所有簇初始化为零来扩展它。这通常是我们在使用随机写入扩展文件时可能会观察到性能成本的地方。我们可以设置 FileValidDataLengthInformation 来解决这个问题(例如 SetFileValidData),但它会暴露未初始化的磁盘数据,因此需要 SeManageVolumePrivilege。使用此功能的应用程序应注意以独占方式打开文件并确保文件安全,以防应用程序或系统崩溃。