[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%
```
This commit is contained in:
Nikolas Klauser 2026-02-26 12:01:07 +01:00 committed by GitHub
parent 23dffff410
commit b101f01382
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 144 additions and 14 deletions

View File

@ -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<int>`` iterators.
- ``std::copy(CharT*, CharT*, ostreambuf_iterator<CharT>)`` has been optimized, resulting in performance improvements
of up to 25x.
Deprecations and Removals
-------------------------

View File

@ -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 <iosfwd> // 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 <class, class...>
friend struct __specialized_algorithm;
};
template <class _InCharT, class _CharT, class _Traits>
struct __specialized_algorithm<_Algorithm::__copy,
__iterator_pair<_InCharT*, _InCharT*>,
__single_iterator<ostreambuf_iterator<_CharT, _Traits> > > {
static const bool __has_algorithm = is_same<const _CharT, const _InCharT>::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

View File

@ -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;
}

View File

@ -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 <iterator>
#include <fstream>
#include <vector>
#include <benchmark/benchmark.h>
static void bm_copy(benchmark::State& state) {
std::vector<char> buffer;
buffer.resize(16384);
std::ofstream stream("/dev/null");
for (auto _ : state)
std::copy(buffer.begin(), buffer.end(), std::ostreambuf_iterator<char>(stream.rdbuf()));
}
BENCHMARK(bm_copy)->Name("std::copy(CharT*, CharT*, ostreambuf_iterator)");
BENCHMARK_MAIN();

View File

@ -13,6 +13,7 @@
// ADDITIONAL_COMPILE_FLAGS: -Wno-c++14-extensions -Wno-c++17-extensions
#include <algorithm>
#include <iterator>
#include <map>
#include <set>
#include <vector>
@ -81,3 +82,14 @@ static_assert(has_alg<Alg::__for_each, iter_pair<const_iter, const_iter>>);
static_assert(has_alg<Alg::__for_each, single_range<std::multimap<int, int>>>);
#endif
} // namespace multimap
namespace ostreambuf_iterator {
template <class CharT>
using obi = std::ostreambuf_iterator<CharT>;
static_assert(has_alg<Alg::__copy, iter_pair<char*, char*>, single_iter<obi<char>>>);
static_assert(has_alg<Alg::__copy, iter_pair<const char*, const char*>, single_iter<obi<char>>>);
static_assert(has_alg<Alg::__copy, iter_pair<wchar_t*, wchar_t*>, single_iter<obi<wchar_t>>>);
static_assert(has_alg<Alg::__copy, iter_pair<const wchar_t*, const wchar_t*>, single_iter<obi<wchar_t>>>);
static_assert(!has_alg<Alg::__copy, iter_pair<char*, char*>, single_iter<obi<wchar_t>>>);
} // namespace ostreambuf_iterator

View File

@ -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
//
//===----------------------------------------------------------------------===//
// <algorithm>
// template<class CharT, class Traits>
// constexpr OutIter
// copy(CharT* first, CharT* last, ostreambuf_iterator<CharT, Traits> result);
// UNSUPPORTED: no-localization
#include <algorithm>
#include <cassert>
#include <iterator>
#include <sstream>
#include <type_traits>
#include "stream_types.h"
#include "test_macros.h"
template <class CCharT>
void test() {
using CharT = typename std::remove_cv<CCharT>::type;
{
std::basic_ostringstream<CharT> oss;
CCharT buff[] = {'B', 'a', 'n', 'a', 'n', 'e'};
std::copy(std::begin(buff), std::end(buff), std::ostreambuf_iterator<CharT>(oss));
assert(oss.str() == std::basic_string_view<CharT>(buff, 6));
}
{
failing_streambuf<CharT> fsb(4);
std::basic_ostream<CharT> oss(&fsb);
CCharT buff[] = {'B', 'a', 'n', 'a', 'n', 'e'};
auto res = std::copy(std::begin(buff), std::end(buff), std::ostreambuf_iterator<CharT>(oss));
assert(res.failed());
assert(fsb.str() == std::basic_string_view<CharT>(buff, 4));
}
}
int main(int, char**) {
test<char>();
test<const char>();
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
test<wchar_t>();
test<const wchar_t>();
#endif
return 0;
}

View File

@ -41,4 +41,30 @@ private:
size_t index_;
};
template <class CharT>
class failing_streambuf : public std::basic_streambuf<CharT> {
using char_type = CharT;
using traits_type = std::char_traits<CharT>;
using int_type = typename traits_type::int_type;
public:
failing_streambuf(size_t fail_at) : fail_at_(fail_at) {}
std::basic_string<char_type> 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<char_type> underlying_data_;
size_t fail_at_;
};
#endif // TEST_SUPPORT_STREAM_TYPES_H