From b101f01382d6ff53cebeedbcbf114d02947b10ce Mon Sep 17 00:00:00 2001 From: Nikolas Klauser Date: Thu, 26 Feb 2026 12:01:07 +0100 Subject: [PATCH] [libc++] Optimize using std::copy with an ostreambuf_iterator (#181815) ``` Benchmark old new Difference % Difference ---------------------------------------------- -------------- -------------- ------------ -------------- std::copy(CharT*,_CharT*,_ostreambuf_iterator) 8115.45 329.54 -7785.91 -95.94% ``` --- libcxx/docs/ReleaseNotes/23.rst | 2 + .../include/__iterator/ostreambuf_iterator.h | 23 ++++++++ libcxx/include/__locale_dir/pad_and_output.h | 16 +----- libcxx/test/benchmarks/streams/copy.bench.cpp | 26 +++++++++ .../specialized_algorithms.compile.pass.cpp | 12 +++++ .../alg.copy/ostreambuf.copy.pass.cpp | 53 +++++++++++++++++++ libcxx/test/support/stream_types.h | 26 +++++++++ 7 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 libcxx/test/benchmarks/streams/copy.bench.cpp create mode 100644 libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ostreambuf.copy.pass.cpp diff --git a/libcxx/docs/ReleaseNotes/23.rst b/libcxx/docs/ReleaseNotes/23.rst index b8a8a088abec..9519f5b6c293 100644 --- a/libcxx/docs/ReleaseNotes/23.rst +++ b/libcxx/docs/ReleaseNotes/23.rst @@ -46,6 +46,8 @@ Improvements and New Features - The ``std::ranges::fold_left_with_iter`` algorithm has been optimized for segmented iterators, resulting in a performance improvement of up to 1.38x for ``std::deque`` iterators. +- ``std::copy(CharT*, CharT*, ostreambuf_iterator)`` has been optimized, resulting in performance improvements + of up to 25x. Deprecations and Removals ------------------------- diff --git a/libcxx/include/__iterator/ostreambuf_iterator.h b/libcxx/include/__iterator/ostreambuf_iterator.h index 4a3b2fa02449..588844c4ec11 100644 --- a/libcxx/include/__iterator/ostreambuf_iterator.h +++ b/libcxx/include/__iterator/ostreambuf_iterator.h @@ -10,6 +10,7 @@ #ifndef _LIBCPP___ITERATOR_OSTREAMBUF_ITERATOR_H #define _LIBCPP___ITERATOR_OSTREAMBUF_ITERATOR_H +#include <__algorithm/specialized_algorithms.h> #include <__config> #include <__cstddef/ptrdiff_t.h> #include <__fwd/ios.h> @@ -17,6 +18,8 @@ #include <__fwd/streambuf.h> #include <__iterator/iterator.h> #include <__iterator/iterator_traits.h> +#include <__type_traits/is_same.h> +#include <__utility/pair.h> #include // for forward declaration of ostreambuf_iterator #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -64,6 +67,26 @@ public: friend _LIBCPP_HIDE_FROM_ABI ostreambuf_iterator<_Ch, _Tr> __pad_and_output( ostreambuf_iterator<_Ch, _Tr> __s, const _Ch* __ob, const _Ch* __op, const _Ch* __oe, ios_base& __iob, _Ch __fl); #endif // _LIBCPP_HAS_LOCALIZATION + + template + friend struct __specialized_algorithm; +}; + +template +struct __specialized_algorithm<_Algorithm::__copy, + __iterator_pair<_InCharT*, _InCharT*>, + __single_iterator > > { + static const bool __has_algorithm = is_same::value; + + _LIBCPP_HIDE_FROM_ABI static pair<_InCharT*, ostreambuf_iterator<_CharT, _Traits> > + operator()(_InCharT* __first, _InCharT* __last, ostreambuf_iterator<_CharT, _Traits> __result) { + auto __size = __last - __first; + if (__result.__sbuf_ && __size > 0) { + if (__result.__sbuf_->sputn(__first, __last - __first) != __size) + __result.__sbuf_ = nullptr; + } + return pair<_InCharT*, ostreambuf_iterator<_CharT, _Traits> >(__last, __result); + } }; _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/__locale_dir/pad_and_output.h b/libcxx/include/__locale_dir/pad_and_output.h index bdd4d2856dad..4b845792a308 100644 --- a/libcxx/include/__locale_dir/pad_and_output.h +++ b/libcxx/include/__locale_dir/pad_and_output.h @@ -55,13 +55,7 @@ _LIBCPP_HIDE_FROM_ABI ostreambuf_iterator<_CharT, _Traits> __pad_and_output( __ns -= __sz; else __ns = 0; - streamsize __np = __op - __ob; - if (__np > 0) { - if (__s.__sbuf_->sputn(__ob, __np) != __np) { - __s.__sbuf_ = nullptr; - return __s; - } - } + __s = std::copy(__ob, __op, __s); if (__ns > 0) { basic_string<_CharT, _Traits> __sp(__ns, __fl); if (__s.__sbuf_->sputn(__sp.data(), __ns) != __ns) { @@ -69,13 +63,7 @@ _LIBCPP_HIDE_FROM_ABI ostreambuf_iterator<_CharT, _Traits> __pad_and_output( return __s; } } - __np = __oe - __op; - if (__np > 0) { - if (__s.__sbuf_->sputn(__op, __np) != __np) { - __s.__sbuf_ = nullptr; - return __s; - } - } + __s = std::copy(__op, __oe, __s); __iob.width(0); return __s; } diff --git a/libcxx/test/benchmarks/streams/copy.bench.cpp b/libcxx/test/benchmarks/streams/copy.bench.cpp new file mode 100644 index 000000000000..2f01e81e2b23 --- /dev/null +++ b/libcxx/test/benchmarks/streams/copy.bench.cpp @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include + +static void bm_copy(benchmark::State& state) { + std::vector buffer; + buffer.resize(16384); + + std::ofstream stream("/dev/null"); + + for (auto _ : state) + std::copy(buffer.begin(), buffer.end(), std::ostreambuf_iterator(stream.rdbuf())); +} +BENCHMARK(bm_copy)->Name("std::copy(CharT*, CharT*, ostreambuf_iterator)"); + +BENCHMARK_MAIN(); diff --git a/libcxx/test/libcxx/algorithms/specialized_algorithms.compile.pass.cpp b/libcxx/test/libcxx/algorithms/specialized_algorithms.compile.pass.cpp index 63084252a9ce..22e44f897d57 100644 --- a/libcxx/test/libcxx/algorithms/specialized_algorithms.compile.pass.cpp +++ b/libcxx/test/libcxx/algorithms/specialized_algorithms.compile.pass.cpp @@ -13,6 +13,7 @@ // ADDITIONAL_COMPILE_FLAGS: -Wno-c++14-extensions -Wno-c++17-extensions #include +#include #include #include #include @@ -81,3 +82,14 @@ static_assert(has_alg>); static_assert(has_alg>>); #endif } // namespace multimap + +namespace ostreambuf_iterator { +template +using obi = std::ostreambuf_iterator; +static_assert(has_alg, single_iter>>); +static_assert(has_alg, single_iter>>); +static_assert(has_alg, single_iter>>); +static_assert(has_alg, single_iter>>); +static_assert(!has_alg, single_iter>>); + +} // namespace ostreambuf_iterator diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ostreambuf.copy.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ostreambuf.copy.pass.cpp new file mode 100644 index 000000000000..f21e81bac06e --- /dev/null +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ostreambuf.copy.pass.cpp @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// template +// constexpr OutIter +// copy(CharT* first, CharT* last, ostreambuf_iterator result); + +// UNSUPPORTED: no-localization + +#include +#include +#include +#include +#include + +#include "stream_types.h" +#include "test_macros.h" + +template +void test() { + using CharT = typename std::remove_cv::type; + { + std::basic_ostringstream oss; + CCharT buff[] = {'B', 'a', 'n', 'a', 'n', 'e'}; + std::copy(std::begin(buff), std::end(buff), std::ostreambuf_iterator(oss)); + assert(oss.str() == std::basic_string_view(buff, 6)); + } + { + failing_streambuf fsb(4); + std::basic_ostream oss(&fsb); + CCharT buff[] = {'B', 'a', 'n', 'a', 'n', 'e'}; + auto res = std::copy(std::begin(buff), std::end(buff), std::ostreambuf_iterator(oss)); + assert(res.failed()); + assert(fsb.str() == std::basic_string_view(buff, 4)); + } +} + +int main(int, char**) { + test(); + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); + test(); +#endif + return 0; +} diff --git a/libcxx/test/support/stream_types.h b/libcxx/test/support/stream_types.h index 7817fc33e914..3826bd79a58a 100644 --- a/libcxx/test/support/stream_types.h +++ b/libcxx/test/support/stream_types.h @@ -41,4 +41,30 @@ private: size_t index_; }; +template +class failing_streambuf : public std::basic_streambuf { + using char_type = CharT; + using traits_type = std::char_traits; + using int_type = typename traits_type::int_type; + +public: + failing_streambuf(size_t fail_at) : fail_at_(fail_at) {} + + std::basic_string str() const { return underlying_data_; } + +protected: + int_type overflow(int_type c) override { + if (underlying_data_.size() == fail_at_) + return traits_type::eof(); + if (traits_type::eq_int_type(c, traits_type::eof())) + return traits_type::not_eof(c); + underlying_data_.push_back(traits_type::to_char_type(c)); + return c; + } + +private: + std::basic_string underlying_data_; + size_t fail_at_; +}; + #endif // TEST_SUPPORT_STREAM_TYPES_H