4

假设我有多个进程写入大文件(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。

Systernals DiskView 还确认集群已分配 迪克视图

当按下回车键以允许测试继续时(并等待相当长的时间,因为文件是在慢速 USB 记忆棒上创建的),该StreamDataSize字段已更新 在此处输入图像描述

由于我最后写了 1 个字节,NTFS 现在真的必须将所有内容归零,所以SetEndOfFile确实有助于解决我“烦恼”的问题。

我非常感谢答案/评论也提供了官方参考来支持所提出的主张。

哦,在我的情况下,测试应用程序会输出这个:

creating file
file extended, elapsed: 0

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

同样为了完整起见,这里是一个示例,设置时 DATA 属性的外观FileAllocationInfo(请注意,我为此图片创建了一个新文件) 在此处输入图像描述

4

1 回答 1

2

Windows 文件系统为文件数据维护两种公共大小,它们在 中报告FileStandardInformation

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

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

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

如果你不介意直接使用 NT API,你也可以调用NtSetInformationFile: FileAllocationInformation。甚至在创建时通过NtCreateFile.


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

于 2018-11-16T20:01:56.667 回答