[PATCH] libstdc++: optimize std::for_each for segmented iterators

Jonathan Wakely jwakely@redhat.com
Wed Aug 19 11:38:32 GMT 2026


On Mon, 03 Aug 2026 at 23:11 +0800, Yuao Ma wrote:
>On Mon, Aug 3, 2026 at 9:22 PM Patrick Palka <ppalka@redhat.com> 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 <c8ef@outlook.com>
>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
>



More information about the Libstdc++ mailing list