[libcxx-commits] [libcxx] [libc++] Always disable buffering of the stream underlying std::filebuf (PR #209872)
Nikolas Klauser via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Oct 20 08:06:27 PDT 2026
https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/209872
>From 54bd8a2e5b527914b575978b36efed8e9c0bbc5a Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Wed, 15 Jul 2026 21:40:13 +0200
Subject: [PATCH] [libc++] Always disable buffering of the stream underlying
std::filebuf
---
libcxx/docs/ReleaseNotes/24.rst | 2 +
libcxx/include/fstream | 70 +++----------------
.../test/benchmarks/streams/fstream.bench.cpp | 16 +++--
.../fstreams/filebuf.virtuals/setbuf.pass.cpp | 4 +-
4 files changed, 24 insertions(+), 68 deletions(-)
diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst
index 04f02ab66a773c..e69380561f913a 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -62,6 +62,8 @@ Improvements and New Features
-----------------------------
- ``basic_filebuf`` now supports files larger than 2GB on 32-bit AIX and 32-bit glibc targets.
+- ``std::fstream`` disables buffering of the underlying C stream now to avoid double buffering and to better respect the
+ users requests, improving performance by up to 2x.
Deprecations and Removals
-------------------------
diff --git a/libcxx/include/fstream b/libcxx/include/fstream
index 749abe971594a9..c1c6db123105f3 100644
--- a/libcxx/include/fstream
+++ b/libcxx/include/fstream
@@ -359,43 +359,7 @@ private:
state_type __st_;
state_type __st_last_;
ios_base::openmode __om_;
- // There have been no file operations yet, which allows setting unbuffered
- // I/O mode.
- static const ios_base::openmode __no_io_operations = ios_base::trunc;
- // Unbuffered I/O mode has been requested.
- static const ios_base::openmode __use_unbuffered_io = ios_base::ate;
- // Used to track the currently used mode and track whether the output should
- // be unbuffered.
- // [filebuf.virtuals]/12
- // If setbuf(0, 0) is called on a stream before any I/O has occurred on
- // that stream, the stream becomes unbuffered. Otherwise the results are
- // implementation-defined.
- // This allows calling setbuf(0, 0)
- // - before opening a file,
- // - after opening a file, before
- // - a read
- // - a write
- // - a seek.
- // Note that opening a file with ios_base::ate does a seek operation.
- // Normally underflow, overflow, and sync change this flag to ios_base::in,
- // ios_base_out, or 0.
- //
- // The ios_base::trunc and ios_base::ate flags are not used in __cm_. They
- // are used to track the state of the unbuffered request. For readability
- // they have the aliases __no_io_operations and __use_unbuffered_io
- // respectively.
- //
- // The __no_io_operations and __use_unbuffered_io flags are used in the
- // following way:
- // - __no_io_operations is set upon construction to indicate the unbuffered
- // state can be set.
- // - When requesting unbuffered output:
- // - If the file is open it sets the mode.
- // - Else places a request by adding the __use_unbuffered_io flag.
- // - When a file is opened it checks whether both __no_io_operations and
- // __use_unbuffered_io are set. If so switches to unbuffered mode.
- // - All file I/O operations change the mode effectively clearing the
- // __no_io_operations and __use_unbuffered_io flags.
+ // Used to track the currently used mode.
ios_base::openmode __cm_;
bool __owns_eb_;
bool __owns_ib_;
@@ -453,11 +417,6 @@ public:
return nullptr;
__om_ = __mode;
- if (__cm_ == (__no_io_operations | __use_unbuffered_io)) {
- std::setbuf(__file_, nullptr);
- __cm_ = 0;
- }
-
if (__mode & ios_base::ate) {
__cm_ = 0;
if (fseek(__file_, 0, SEEK_END)) {
@@ -471,18 +430,6 @@ public:
}
private:
- // If the file is already open, switch to unbuffered mode. Otherwise, record
- // the request to use unbuffered mode so that we use that mode when we
- // eventually open the file.
- _LIBCPP_HIDE_FROM_ABI void __request_unbuffered_mode() {
- if (__file_) {
- std::setbuf(__file_, nullptr);
- __cm_ = 0;
- } else {
- __cm_ = __no_io_operations | __use_unbuffered_io;
- }
- }
-
_LIBCPP_HIDE_FROM_ABI typename traits_type::int_type __overflow_failed() {
if (this->pptr() == this->epptr() + 1) {
this->pbump(-1); // lose the character we overflowed above -- we don't really have a
@@ -590,7 +537,7 @@ basic_filebuf<_CharT, _Traits>::basic_filebuf()
__st_(),
__st_last_(),
__om_(0),
- __cm_(__no_io_operations),
+ __cm_(),
__owns_eb_(false),
__owns_ib_(false),
__always_noconv_(false) {
@@ -892,7 +839,12 @@ basic_filebuf<_CharT, _Traits>* basic_filebuf<_CharT, _Traits>::open(const char*
if (!__mdstr)
return nullptr;
- return __adopt_file(__fopen(__s, __mdstr), __mode);
+ if (FILE* __file = __fopen(__s, __mdstr)) {
+ // We already buffer at the C++ level - no need to buffer in the C stream as well
+ std::setbuf(__file, nullptr);
+ return __adopt_file(__file, __mode);
+ }
+ return nullptr;
}
template <class _CharT, class _Traits>
@@ -943,7 +895,7 @@ basic_filebuf<_CharT, _Traits>* basic_filebuf<_CharT, _Traits>::close() {
// since the user may re-open the stream.
this->setg(nullptr, nullptr, nullptr);
this->setp(nullptr, nullptr);
- __cm_ = __no_io_operations;
+ __cm_ = 0;
}
return __rt;
}
@@ -1097,9 +1049,7 @@ template <class _CharT, class _Traits>
basic_streambuf<_CharT, _Traits>* basic_filebuf<_CharT, _Traits>::setbuf(char_type* __s, streamsize __n) {
this->setg(nullptr, nullptr, nullptr);
this->setp(nullptr, nullptr);
- // Calling setbuf(nullptr, 0) before any i/o operation switches the stream to unbuffered mode
- if (__cm_ == __no_io_operations && __s == nullptr && __n == 0)
- __request_unbuffered_mode();
+ __cm_ = 0;
if (__owns_eb_)
delete[] __extbuf_;
if (__owns_ib_)
diff --git a/libcxx/test/benchmarks/streams/fstream.bench.cpp b/libcxx/test/benchmarks/streams/fstream.bench.cpp
index a1b55cf366aa60..745dd253d63c47 100644
--- a/libcxx/test/benchmarks/streams/fstream.bench.cpp
+++ b/libcxx/test/benchmarks/streams/fstream.bench.cpp
@@ -15,20 +15,24 @@
#include <benchmark/benchmark.h>
#include "test_macros.h"
+auto run_sizes(benchmark::Benchmark* benchmark) { benchmark->Arg(120)->Arg(4000)->Arg(16384); }
+
static TEST_ALIGN_BENCHMARK void bm_ofstream_write(benchmark::State& state) {
std::vector<char> buffer;
- buffer.resize(16384);
+ buffer.resize(state.range());
- std::ofstream stream("/dev/null");
+ std::ofstream stream("testfile");
- for (auto _ : state)
+ for (auto _ : state) {
stream.write(buffer.data(), buffer.size());
+ stream.seekp(0);
+ }
}
-BENCHMARK(bm_ofstream_write)->Name("std::ofstream::write(char*, size)");
+BENCHMARK(bm_ofstream_write)->Name("std::ofstream::write(char*, size)")->Apply(run_sizes);
static TEST_ALIGN_BENCHMARK void bm_ifstream_read(benchmark::State& state) {
std::vector<char> buffer;
- buffer.resize(16384);
+ buffer.resize(state.range());
{
std::ofstream gen_testfile("testfile");
@@ -44,7 +48,7 @@ static TEST_ALIGN_BENCHMARK void bm_ifstream_read(benchmark::State& state) {
stream.seekg(0);
}
}
-BENCHMARK(bm_ifstream_read)->Name("std::ifstream::read(char*, size)");
+BENCHMARK(bm_ifstream_read)->Name("std::ifstream::read(char*, size)")->Apply(run_sizes);
void run_sizes(benchmark::Benchmark* benchmark) { benchmark->Arg(0)->Arg(100)->Arg(4000)->Arg(10000); }
diff --git a/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/setbuf.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/setbuf.pass.cpp
index 72af0a2db11804..f1f762c0954852 100644
--- a/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/setbuf.pass.cpp
+++ b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/setbuf.pass.cpp
@@ -97,8 +97,8 @@ static void unbuffered_request_after_open_ate() {
buffer.sputc(CharT('a'));
assert(file_size<CharT>("test.dat") <= 1);
- // on libc++ buffering is used by default.
- LIBCPP_ASSERT(file_size<CharT>("test.dat") == 0);
+ // libc++ doesn't buffer if the buffer is explicitly removed.
+ LIBCPP_NON_FROZEN_ASSERT(file_size<CharT>("test.dat") == 1);
buffer.close();
assert(file_size<CharT>("test.dat") == 1);
More information about the libcxx-commits
mailing list