[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
Mon Jul 20 01:41:03 PDT 2026


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/209872

>From 71721e6a480427a3410333e46cb62cd58cc30c39 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               |  3 +-
 libcxx/include/fstream                        | 70 +++----------------
 .../fstreams/filebuf.virtuals/setbuf.pass.cpp |  4 +-
 3 files changed, 14 insertions(+), 63 deletions(-)

diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst
index bcac37b8a717f..163a8cc6c37db 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -42,6 +42,8 @@ Implemented Papers
 Improvements and New Features
 -----------------------------
 
+- ``std::fstream`` disables buffering of the underlying stream now to avoid double buffering and to better respect the
+  users requests, improving performance by up to 2x.
 
 Deprecations and Removals
 -------------------------
@@ -71,4 +73,3 @@ ABI Affecting Changes
 
 Build System Changes
 --------------------
-
diff --git a/libcxx/include/fstream b/libcxx/include/fstream
index 7b84bf6609086..6c14978b6b1b5 100644
--- a/libcxx/include/fstream
+++ b/libcxx/include/fstream
@@ -341,43 +341,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_;
@@ -400,11 +364,6 @@ private:
       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)) {
@@ -417,18 +376,6 @@ private:
     return this;
   }
 
-  // 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
@@ -451,7 +398,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) {
@@ -742,7 +689,12 @@ basic_filebuf<_CharT, _Traits>* basic_filebuf<_CharT, _Traits>::open(const char*
   if (!__mdstr)
     return nullptr;
 
-  return __do_open(std::fopen(__s, __mdstr), __mode);
+  if (FILE* __file = std::fopen(__s, __mdstr)) {
+    // We already buffer - no need to buffer twice
+    std::setbuf(__file, nullptr);
+    return __do_open(__file, __mode);
+  }
+  return nullptr;
 }
 
 template <class _CharT, class _Traits>
@@ -793,7 +745,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;
 }
@@ -949,9 +901,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/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 72af0a2db1180..f1f762c095485 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