C++中的缓冲区大小

7
我注意到使用C++标准库方法std::ostream::write()时出现了以下行为。
为了缓冲数据,我正在使用以下C++ API。
std::ofstream::rdbuf()->pubsetbuf(char* s, streamsize n)

这个代码可以很好地工作(使用strace实用程序进行验证),只要我们在文件流上写入的数据大小(datasize)不大于指定的缓冲区大小。
std::ofstream::write (const char* s, datasize n)

当写入的数据小于1023字节时(在此值以下,写入将被累积,直到缓冲区不再为空),但当要写入的数据大小超过1023时,缓冲区将不会被考虑,数据将被刷新到文件中。

例如,如果我将缓冲区大小设置为10KB,并每次写入约512字节,则strace将显示多个写操作已合并为单个写入操作。

writev(3, [{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 9728}, {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 512}], 2) = 10240 ( 10 KB )
writev(3, [{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 9728}, {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 512}], 2) = 10240
...

但是,当我每次写入1024字节(将缓冲区保持为10 KB)时,现在strace显示它没有使用缓冲区,并且每个ofstream::write调用都被转换为写系统调用。

writev(3, [{NULL, 0}, {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 1024}], 2) = 1024 ( 1KB )
writev(3, [{NULL, 0}, {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 1024}], 2) = 1024
...

我是否遗漏了任何C++ API调用或Linux优化参数?


没有这样的东西。有std::flush操作符,但它会强制刷新缓冲区,这与OP想要的相反。 - Sebastian Redl
@SebastianRedl 老实说,楼主甚至没有说明他想要什么。 - Bartek Banachewicz
每次写入1024字节是高效的。在现代架构中,通过进行更大的写入几乎没有什么好处。 - Klas Lindbäck
4
@KlasLindbäck 我不同意:拥有10 GB/s的内存总线带宽,复制1 kiB大约需要100纳秒。系统调用一般需要几微秒。我认为,大约需要100 kiB才能使系统调用开销变得可以忽略不计。 - cmaster - reinstate monica
实际上,我正在尝试减少应用程序执行的写系统调用数量,C++ 的默认缓冲区大小约为8KB(在累积了8KB的数据后进行一次写系统调用,但当我要写入的数据小于1K时也可以工作)。 - Abhishek
显示剩余2条评论
2个回答

3
这是libstdc++的一个实现细节,实现在bits/fstream.tcc的650行左右。基本上,如果写入的内容大于2^10,则会跳过缓冲区。
如果您想了解这个决定背后的原因,建议您发送邮件到libstdc++开发列表。 http://gcc.gnu.org/ml/libstdc++/

已经向libstdc++邮件列表发送了一封邮件,是的,这种行为有点硬编码。此外,我在Sun OS上执行了相同的测试用例(使用Sun Studio CC编译器),那里的行为是不同的,它会尊重给定的缓冲区(没有1024字节的边界)。 - Abhishek
这似乎今天仍然有效(在此处找到硬编码常量 https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/bits/fstream.tcc#L768)。 - iMineLink

1

看起来某人在编写stdlib实现时进行了“优化”,但没有充分考虑。因此,你唯一的解决方法是避免使用C++ API,而是使用标准的C库。

这不是GNU/Linux标准C++库实现中唯一的次优性:在我的机器上,malloc()比标准的void* operator new (size_t size)快100个周期...


new() 调用构造函数,而 malloc() 不会。并且它还做了更多好事。比较两者的速度只是无意义的。 - scai
1
@scai 很抱歉反驳你,但这正是我给出整个签名的原因:我不是在谈论 new 关键字,甚至不是在谈论类特定的 new 运算符,而是在谈论 一个全局函数,它仅执行分配操作,没有其他任何功能。这个函数可以通过调用 malloc() 轻松实现,这也是我的测量基础。 - cmaster - reinstate monica

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