Re: [PATCH] libstdc++: optimize std::for_each for segmented iterators
Jonathan Wakely <[email protected]>
| Newsgroups | gmane.comp.gcc.patches,gmane.comp.gcc.libstdc++.devel |
|---|---|
| Message-ID | <[email protected]> |
On Mon, 03 Aug 2026 at 23:11 +0800, Yuao Ma wrote: >On Mon, Aug 3, 2026 at 9:22 PM Patrick Palka <[email protected]> wrote: >> >> On Sun, 2 Aug 2026, Yuao Ma wrote: >> >> > Hi! >> > >> > Similar to ranges::distance, this patch optimizes std::for_each for >> > segmented iterators. >> >> Thanks for working on this. >> >> > >> > Fully tested on x86_64-linux with no regressions. >> > >> > Using the newly added benchmark, it shows a 3x improvement when using >> > std::for_each with std::deque. >> > >> > === Sun Aug 2 09:30:52 AM UTC 2026 === >> > for_each.cc std::for_each vector<int> 2r 1u 0s >> > 0mem 0pf >> > for_each.cc std::for_each deque<int> 6r 6u 0s >> > 0mem 0pf >> > for_each.cc std::for_each list<int> 13r 13u 0s >> > 0mem 0pf >> > === Sun Aug 2 09:31:08 AM UTC 2026 === >> > for_each.cc std::for_each vector<int> 2r 1u 0s >> > 0mem 0pf >> > for_each.cc std::for_each deque<int> 2r 2u 0s >> > 0mem 0pf >> > for_each.cc std::for_each list<int> 13r 13u 0s >> > 0mem 0pf >> >> Interesting that even for_each benefits from this! So this assumes >> it's significantly cheaper to iterate over the inner/local iterators >> than over the segmented iterators in general, makes sense. >> >> > >> > Please take a look when you are available, thanks! >> > >> > Yuao >> > >> >> > Subject: [PATCH] libstdc++: optimize std::for_each for segmented iterators >> > >> > Similar to r17-2859-g15505d012872dd, we can optimize std::for_each for >> > segemented iterators. >> > >> > libstdc++-v3/ChangeLog: >> > >> > * include/bits/stl_algo.h (__for_each): Add segemented >> > iterators logic. Split out naive for-loop from ... >> > (for_each): ... here. >> > * testsuite/performance/25_algorithms/for_each.cc: New test. >> > --- >> > libstdc++-v3/include/bits/stl_algo.h | 29 ++++++++++++++-- >> > .../performance/25_algorithms/for_each.cc | 34 +++++++++++++++++++ >> > 2 files changed, 61 insertions(+), 2 deletions(-) >> > create mode 100644 libstdc++-v3/testsuite/performance/25_algorithms/for_each.cc >> > >> > diff --git a/libstdc++-v3/include/bits/stl_algo.h b/libstdc++-v3/include/bits/stl_algo.h >> > index 800c176cd5b..54d73ae8c62 100644 >> > --- a/libstdc++-v3/include/bits/stl_algo.h >> > +++ b/libstdc++-v3/include/bits/stl_algo.h >> > @@ -132,6 +132,32 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > return __first; >> > } >> > >> > + /// Apply __f to each element in [__first, __last) >> > + /// Dispatches to __for_each_segment for segmented iterators >> > + /// (e.g. deque::iterator). >> > + /// Returns an iterator equal to __last. >> > + template<typename _InputIterator, typename _Function> >> > + _GLIBCXX20_CONSTEXPR >> > + _InputIterator >> > + __for_each(_InputIterator __first, _InputIterator __last, _Function& __f) >> > + { >> > +#if __cplusplus >= 201703L >> > + if constexpr (__enable_for_each_segment<_InputIterator>) >> > + { >> > + std::__for_each_segment(__first, __last, >> > + [&](auto __lfirst, auto __llast) >> > + { return std::__for_each(__lfirst, __llast, __f); }); >> > + return __last; >> > + } >> > + else >> > +#endif // C++17 >> >> I think we could enable this optimization as far back as C++11 if we >> use a lambda template here (and #pragma GCC diagnostic ignored etc): >> >> [&]<template _It>(_It __lfirst, _It __llast) >> > >Thanks for the suggestions. I use this approach in the new patch. > >> Besides that, looks good. >> >> (GCC and Clang accept lambda templates even in C++98 mode so in theory >> we could enable the optimization unconditionally if we resort to using >> C++98 compatible SFINAE, but I don't think it's worth it personally.) >> >> > + { >> > + for (; __first != __last; ++__first) >> > + __f(*__first); >> > + return __first; >> > + } >> > + } >> > + >> > // set_difference >> > // set_intersection >> > // set_symmetric_difference >> > @@ -3813,8 +3839,7 @@ _GLIBCXX_BEGIN_NAMESPACE_ALGO >> > // concept requirements >> > __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>) >> > __glibcxx_requires_valid_range(__first, __last); >> > - for (; __first != __last; ++__first) >> > - __f(*__first); >> > + std::__for_each(__first, __last, __f); >> >> I guess you're factoring this out to use elsewhere, e.g. ranges::for_each? >> > >Hmm, it is mostly for support the stateful functor passed in the >std::for_each, in which case we really need the parameter to be a >reference type. To use it for ranges::for_each at least it needs two >refactor: for function parameter it needs _Sentinel and _Proj, for the >function body it needs std::invoke. Not sure how to correctly includes >these given the current header structure, I think I will have a >standalone optimization for ranges::for_each, and let other algorithms >that could benefit from segmented iterators depend on either of them. > >> > return __f; // N.B. [alg.foreach] says std::move(f) but it's redundant. >> > } >> > >> > diff --git a/libstdc++-v3/testsuite/performance/25_algorithms/for_each.cc b/libstdc++-v3/testsuite/performance/25_algorithms/for_each.cc >> > new file mode 100644 >> > index 00000000000..22981eb9b64 >> > --- /dev/null >> > +++ b/libstdc++-v3/testsuite/performance/25_algorithms/for_each.cc >> > @@ -0,0 +1,34 @@ >> > +// STD=gnu++17 >> > + >> > +#include <testsuite_performance.h> >> > + >> > +#include <algorithm> >> > +#include <deque> >> > +#include <list> >> > +#include <vector> >> > + >> > +const std::size_t size = 8192; >> > + >> > +template <typename Container> >> > +void bench_seq(const char* label, __gnu_test::time_counter& time, >> > + __gnu_test::resource_counter& resource) { >> > + using T = typename Container::value_type; >> > + Container c(size, 1); >> > + start_counters(time, resource); >> > + for (int i = 0; i < 20000; ++i) >> > + std::for_each(c.begin(), c.end(), >> > + [](T& x) { x = std::clamp<T>(x, 10, 100); }); >> > + stop_counters(time, resource); >> > + report_performance(__FILE__, label, time, resource); >> > + clear_counters(time, resource); >> > +} >> > + >> > +int main() { >> > + using namespace __gnu_test; >> > + time_counter time; >> > + resource_counter resource; >> > + >> > + bench_seq<std::vector<int>>("std::for_each vector<int>", time, resource); >> > + bench_seq<std::deque<int>>("std::for_each deque<int>", time, resource); >> > + bench_seq<std::list<int>>("std::for_each list<int>", time, resource); >> > +} >> > -- >> > 2.54.0 >> > >> >From a8dc4e6bf0304ccf27b408a50ead08d3aff21d66 Mon Sep 17 00:00:00 2001 >From: Yuao Ma <[email protected]> >Date: Mon, 3 Aug 2026 23:05:04 +0800 >Subject: [PATCH] libstdc++: optimize std::for_each for segmented iterators > >Similar to r17-2859-g15505d012872dd, we can optimize std::for_each for >segemented iterators. > >libstdc++-v3/ChangeLog: > > * include/bits/stl_algo.h (__for_each): Add segemented > iterators logic. Split out naive for-loop from ... > (for_each): ... here. > * testsuite/performance/25_algorithms/for_each.cc: New test. OK for trunk with one change requested below ... >--- > libstdc++-v3/include/bits/stl_algo.h | 33 ++++++++++++++++-- > .../performance/25_algorithms/for_each.cc | 34 +++++++++++++++++++ > 2 files changed, 65 insertions(+), 2 deletions(-) > create mode 100644 libstdc++-v3/testsuite/performance/25_algorithms/for_each.cc > >diff --git a/libstdc++-v3/include/bits/stl_algo.h b/libstdc++-v3/include/bits/stl_algo.h >index 800c176cd5b..3cd204dd871 100644 >--- a/libstdc++-v3/include/bits/stl_algo.h >+++ b/libstdc++-v3/include/bits/stl_algo.h >@@ -132,6 +132,36 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > return __first; > } > >+ /// Apply __f to each element in [__first, __last) >+ /// Dispatches to __for_each_segment for segmented iterators >+ /// (e.g. deque::iterator). >+ /// Returns an iterator equal to __last. Just "//" for these comments please, we don't need Doxygen to process this internal implementation details. >+#pragma GCC diagnostic push >+#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr >+#pragma GCC diagnostic ignored "-Wc++20-extensions" // template lambda >+ template<typename _InputIterator, typename _Function> >+ _GLIBCXX20_CONSTEXPR >+ _InputIterator >+ __for_each(_InputIterator __first, _InputIterator __last, _Function& __f) >+ { >+#if __cplusplus >= 201103L >+ if constexpr (__enable_for_each_segment<_InputIterator>) >+ { >+ std::__for_each_segment(__first, __last, >+ [&]<typename _Iter>(_Iter __lfirst, _Iter __llast) >+ { return std::__for_each(__lfirst, __llast, __f); }); >+ return __last; >+ } >+ else >+#endif // C++11 >+ { >+ for (; __first != __last; ++__first) >+ __f(*__first); >+ return __first; >+ } >+ } >+#pragma GCC diagnostic pop >+ > // set_difference > // set_intersection > // set_symmetric_difference >@@ -3813,8 +3843,7 @@ _GLIBCXX_BEGIN_NAMESPACE_ALGO > // concept requirements > __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>) > __glibcxx_requires_valid_range(__first, __last); >- for (; __first != __last; ++__first) >- __f(*__first); >+ std::__for_each(__first, __last, __f); > return __f; // N.B. [alg.foreach] says std::move(f) but it's redundant. > } > >diff --git a/libstdc++-v3/testsuite/performance/25_algorithms/for_each.cc b/libstdc++-v3/testsuite/performance/25_algorithms/for_each.cc >new file mode 100644 >index 00000000000..22981eb9b64 >--- /dev/null >+++ b/libstdc++-v3/testsuite/performance/25_algorithms/for_each.cc >@@ -0,0 +1,34 @@ >+// STD=gnu++17 This is for std::clamp, right? We should probably remove the -std=gnu++11 in scripts/check_performance and just use the default -std option (currently gnu++20). I think the -std=gnu++11 was added when the default was still -std=gnu++98, so that we could test "new" C++11 features. That can be a separate patch though. >+ >+#include <testsuite_performance.h> >+ >+#include <algorithm> >+#include <deque> >+#include <list> >+#include <vector> >+ >+const std::size_t size = 8192; >+ >+template <typename Container> >+void bench_seq(const char* label, __gnu_test::time_counter& time, >+ __gnu_test::resource_counter& resource) { >+ using T = typename Container::value_type; >+ Container c(size, 1); >+ start_counters(time, resource); >+ for (int i = 0; i < 20000; ++i) >+ std::for_each(c.begin(), c.end(), >+ [](T& x) { x = std::clamp<T>(x, 10, 100); }); >+ stop_counters(time, resource); >+ report_performance(__FILE__, label, time, resource); >+ clear_counters(time, resource); >+} >+ >+int main() { >+ using namespace __gnu_test; >+ time_counter time; >+ resource_counter resource; >+ >+ bench_seq<std::vector<int>>("std::for_each vector<int>", time, resource); >+ bench_seq<std::deque<int>>("std::for_each deque<int>", time, resource); >+ bench_seq<std::list<int>>("std::for_each list<int>", time, resource); >+} >-- >2.54.0 >