Microsoft 中的 C++ 标准库文件流操作是否瘫痪?

Are the C++ Standard Library File stream operations crippled in Microsoft?

提问人:Smeghead 提问时间:7/23/2021 最后编辑:MgetzSmeghead 更新时间:7/23/2021 访问量:283

问:

我之所以问这个问题,是因为我一直在做一个需要非常快速地收集大量数据的项目,具体取决于场景。5.7GBytes,大写字节/秒或11.4GB/秒。

我们正在使用 3 个 Samsung Pro NVME 使用小型条纹 raid 阵列(对于 11.4GB/s,我们有一个更大的阵列)。

目前,该项目已在 Windows 上开发,我想使事情尽可能可移植,所以我专注于使用 C++ 标准库;但是,无论我做什么,我都无法破解传输文件的速度超过1.5GB/s

该策略很简单,就是创建几个巨大的交换缓冲区,并将它们作为一个巨大的未格式化二进制文件直接写入磁盘。

通过以下方式使用和基准测试手动设置不同的缓冲区大小:std::ofstream

rdbuf()->pubsetbuf(buffer, BUFFER_SIZE);
open(Filename, std::ios::binary|std::ios::trunc);

其次是我的托管写入循环,我能够找到一个最佳位置,但始终无法破解 1.5GB/s

然后,我找到了 Windows SDK 及其 CreateFile 函数

特别是,使用 FILE_FLAG_NO_BUFFERING 标志的 create file 函数。

这是一个游戏规则的改变者,只要我确保我向它提供扇区对齐的数据(在我的情况下,一切都需要是 512Bytes 的倍数),我突然能够充分利用 raid 阵列吞吐量。

我重新审视了该函数,试图使用更多与操作系统无关的函数;但是,即使可以指定零缓冲区,似乎也没有任何关于在没有缓冲区的情况下使用该函数的任何警告的文档。std::ofstreamstd::ofstream

std::ofstream允许其写入大小为 64 位值,与仅接受 DWORD 设置的 Windows SDK WriteFile 不同,最大写入大小是 512 的最大倍数,如果文件超过 4GB,则必须在循环中管理写入(我的 DO)。uint32_t

这就提出了一个问题,Microsoft 是否根本没有让 C++ 标准库开发人员访问必要的操作系统级系统调用以利用超高速驱动器阵列?还是我在如何充分利用 C++ 标准库方面遗漏了一些东西?

C++ WinAPI 可视化 ++ C++-标准库 高速

评论

3赞 IInspectable 7/23/2021
Microsoft正在为其编译器实现C++标准库。它对 Win32 API 的访问权与任何其他库相同。问题不在于缺乏访问权限。问题在于 C++ 标准库是一个有代价的抽象。这种成本体现在功能和性能方面。如果性能是你需要的,你将不得不放弃跨平台支持,并选择退出POSIX最终公开的功能集。如果您有兴趣,Microsoft 会在 GitHub 上托管他们的“STL”。
1赞 IInspectable 7/23/2021
也就是说,我发现C++的I/O流是它最糟糕的功能之一。它们使用起来很笨拙,并且在概念上遵循“一切都是文件”的口头禅,由于很多原因而被打破。例如,采用非常不同的方法是有原因的。std::format
1赞 Alex Guteniev 7/23/2021
Github 链接: github.com/microsoft/STL .您可能想要创建问题,甚至提出补丁。尽管“最差的C++功能之一”的性能可能优先级太低,但绝对低于C++23功能或修复C++20缺陷。
0赞 phuclv 7/23/2021
最后的“C++ 标准库”必须使用 Win32 API 来访问文件,包括 ,只需检查 MS STL 实现并查看,它是开源的。但块操作通常应作为块而不是流来完成CreateFile
0赞 Mgetz 7/23/2021
@AlexGuteniev MS STL 根据 c 样式标准 io 实现其。因此,它将被踢到拥有通用 CRT IIRC 的 windiv。对于 OP 来说,存在一个错误fstream

答:

3赞 MSalters 7/23/2021 #1

“Microsoft 只是不给 C++ 标准库开发人员......”

你可能会注意到,你正在使用的产品称为 Microsoft Visual Studio。Visual Studio 的标准库开发人员在 Microsoft 工作,尽管与 Windows 开发人员在不同的团队中工作。

原因更简单一些:Visual C++开发人员不可能了解和优化所有可能的使用场景。以如此高的速度进行文本格式设置有点不寻常。请记住,重点是提供. 用于格式化输出到文件。但是对于高速 I/O,无论如何您都需要二进制输出。ostreamoperator<<ofstream

评论

0赞 Mgetz 7/23/2021
更重要的是,整个 MS 实现是按照 C 标准文件、io(fopen 等)实现的。因此,无论如何,任何问题都很可能出在该代码中。但如果没有分析,就很难说出来。fstream
0赞 IInspectable 7/24/2021
fstream不会因为 studio.h 而变慢。 由于 .在 GitHub 上发布通用 CRT 后,您可以分析一些证据。fstreamfstream
0赞 Smeghead 7/24/2021
谢谢,是的,它是 ofstream,我将其作为 ram 的二进制流直接转储到磁盘。
2赞 datenwolf 7/23/2021 #2

坦率地说,你所瞄准的带宽在当前商用硬件的物理极限范围内(16×PCIe.4 为 ~24GByte/s),在我自己的工作中,我发现在不使用“暗魔法”(又名手工制作组装和优化的系统调用代码)的情况下达到 8GByte/s 以上的单核内存传输速率非常具有挑战性, 它涉及仔细对齐内存访问和利用向量扩展。但最重要的是,要达到这些优化级别,需要了解正在处理的数据类型以及预期的访问模式和/或构建缓存中介以适应底层硬件。

这种优化是显而易见的,完全超出了通用标准库的范围。标准库在其实现中必须遵守规范中记载的行为,其中一些要求往往与充分利用底层硬件所必须做的事情相冲突。

所以我很抱歉告诉你,但你可能不得不咬紧牙关,直接使用低级系统 API,绕过标准库。

评论

0赞 MSalters 7/23/2021
Visual Studio 不需要手工制作的程序集;矢量指令可作为半标准内部函数使用。但我同意它不再是标准C++。
0赞 datenwolf 7/23/2021
@MSalters当我遇到这个问题时(我必须对来自高速ADC的数据流进行一些解包和数据转换),我尝试了“一切”:首先我从Duff器件的变体开始,然后添加矢量内部函数,首先只使用矢量化数据类型,然后还使用内置的向量函数。最终,我用最里面的热循环手工制作,并进行了大量的分析,并从编译器生成的代码中汲取灵感。
0赞 datenwolf 7/23/2021
@MSalters问题在于,编译器不知道该数据将如何在“表亲”函数中使用;使用关键字来指示没有发生别名的位置会有所帮助。但最终知道数据将以每个 32kiB 的块形式到达,并被压缩到在单独线程中运行的表亲函数中,这是编译器看不到的。程序员可以处理内存围栏,知道“另一边”发生了什么。restrict
0赞 Smeghead 7/24/2021
我确信 windows Read/WriteFile api 大致等同于 unix/linux 读写,这本来是我的首选。我确实发现确保我的缓冲区与扇区对齐并使用_aligned_malloc函数很有趣。但我本来希望我们正在转向更通用的C++,它可以访问所有操作系统上的一些较低级别的系统调用。
0赞 datenwolf 7/24/2021
@Smeghead问题是,C++标准库不仅要在 Win32 和 POSIX 上工作,还要在所有其他目标上工作。而一些语义需要在单纯的 / / / 之上完成一些额外的工作。正是这种额外的工作,减慢了他们的速度。请记住,您正在意识到这一点,因为您非常接近硬件的理论极限,因此您会感觉到所有这些额外的小成本加起来。99%的其他用户不会注意到它。iostreamReadFileWriteFilereadwrite