【问题标题】:Windows (ReFS,NTFS) file preallocation hintWindows (ReFS​​,NTFS) 文件预分配提示
【发布时间】: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。

Systernals DiskView 还确认已分配集群

当按下回车键允许测试继续时(并等待相当长的时间,因为文件是在慢速 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


【解决方案1】:

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

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

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

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

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


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

【讨论】:

  • 是的,确切地说,调用 SetFileValidData 只会将 StreamDataSize(和 AttributeSize)设置为作为 ValidDataLength 传递的任何内容,而不会将集群归零,因此新文件可能包含敏感信息。似乎 AllocationSize 映射到 DATA 属性的 AttributeSize 字段,而 EndOfFile 映射到 StreamDataSize 字段。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-04
  • 1970-01-01
  • 2012-02-06
  • 2011-07-20
  • 2010-09-20
  • 2011-08-20
相关资源
最近更新 更多