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

4
假设我有多个进程在写入大文件(20GB+)。每个进程都在写入自己的文件,假设该进程一次写入x MB,然后进行一些处理并再次写入x MB等等。
由于文件块在磁盘上连续分配,这种写入模式会导致文件严重碎片化。
当然,通过在打开文件时使用SetEndOfFile来“预分配”文件,然后在关闭之前设置正确的大小,可以轻松解决此问题。但是,现在访问这些文件的远程应用程序可以解析这些正在进行中的文件,显然在文件末尾看到零,并需要更长时间来解析文件。我无法控制这个读取应用程序,因此无法优化它以考虑末尾的零。
另一个肮脏的解决方法是更频繁地运行碎片整理,运行Systernal的contig实用程序或甚至实现自定义“碎片整理器”,该碎片整理器将处理我的文件并将它们的块合并在一起。
另一个更激进的解决方案是实现一个minifilter驱动程序,该驱动程序将报告“虚假”的文件大小。

显然,上面列出的两种解决方案都远非最佳选择。因此,我想知道是否有一种方法可以向文件系统提供文件大小的提示,以便它在驱动器上“保留”连续的空间,但仍向应用程序报告正确的文件大小?

否则,每次写入更大的块显然都有助于减少碎片,但仍然无法解决问题。

编辑:

由于在我的情况下 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结构:
before 该图表明NTFS已为我的文件分配了簇。然而,未命名的DATA属性将StreamDataSize指定为0。
Systernals DiskView也确认已分配簇 DickView 按Enter键以允许测试继续(并且需要相当长的时间,因为文件是在缓慢的USB驱动器上创建的),则StreamDataSize字段将被更新 enter image description here 自从我在末尾写了1个字节,NTFS现在确实必须将所有内容都清零,因此SetEndOfFile确实有助于解决我所担心的问题。
我非常感谢回答/评论也提供官方参考资料来支持所作出的声明。
哦,测试应用程序在我的情况下输出以下内容:
creating file
file extended, elapsed: 0

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

此外,为了完整起见,以下是设置FileAllocationInfo时DATA属性的示例(请注意,我为此图片创建了一个新文件) enter image description here

2
我真的很好奇为什么我的问题收到了负投票,能否请投票者解释一下原因,这样我就可以改进我的问题? - Jaka
那个SetEndOfFile技巧实际上什么也没做,它只是更新了目录项,但并没有分配任何簇。你自己看不出来这一点,这就是一个很好的提示,说明你正在为一个无关紧要的问题而苦恼。 - Hans Passant
可以确认SetEndOfFile()确实可以减少并通常可以防止碎片化。我们在为我们的一个产品实现并行下载以抵消RTT时想知道它是否有帮助,结果发现确实有帮助。找到这个真是太好了。@HansPassant 这就是预期的效果。如果它会立即在阻塞操作中分配,我们将不必要地使应用程序停滞。它只是向文件系统API提供了一个提示,它恰好采取了正确的方式:“如果可能,请保留这么多未碎片化的空间以扩展文件”。 - Zyl
1个回答

2
Windows文件系统维护文件数据的两个公共大小,这些大小在FileStandardInformation中报告:
  • AllocationSize - 文件的分配大小(以字节为单位),通常是扇区或簇大小的倍数。
  • EndOfFile - 作为从文件开头的字节偏移量的文件的绝对文件尾部位置,必须小于或等于分配大小。
设置超过当前分配大小的文件结尾隐式地扩展了分配。将分配大小设置为小于当前文件结尾的大小隐式地截断了文件结尾。
从Windows Vista开始,我们可以通过SetFileInformationByHandleFileAllocationInfo手动扩展分配大小而不修改文件结尾。您可以使用Sysinternals DiskView验证这会为文件分配簇。当文件关闭时,分配将被截断到当前文件结尾。
如果您不介意直接使用NT API,您也可以调用NtSetInformationFileFileAllocationInformation。或者甚至可以通过NtCreateFile在创建时设置分配大小。
注:该段文字涉及计算机编程领域的技术术语,如翻译有误请指出。
FYI,还有一个内部的ValidDataLength大小,必须小于或等于文件末尾。随着文件的增长,磁盘上的簇会懒惰地初始化。超出有效区域的读取返回零。超出有效区域的写入通过将所有簇初始化为零来扩展它,直到写入偏移量。这通常是我们在用随机写扩展文件时可能观察到性能成本的地方。我们可以设置FileValidDataLengthInformation来解决这个问题(例如SetFileValidData),但它会暴露未初始化的磁盘数据,因此需要SeManageVolumePrivilege。利用此功能的应用程序应当注意以独占方式打开文件,并确保文件在应用程序或系统崩溃时是安全的。

是的,调用SetFileValidData只会将StreamDataSize(和AttributeSize)设置为传递的ValidDataLength,而不会清零簇,因此新文件可能包含敏感信息。似乎AllocationSize映射到DATA属性的AttributeSize字段,而EndOfFile映射到StreamDataSize字段。 - Jaka

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