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

Patrick Palka <[email protected]> Mon, 3 Aug 2026 23:12:53 -0400 (EDT)
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.libstdc++.devel
Message-ID <f1658440-ff34-46db-a387-52d3b1e37c2a@idea>
On Tue, 4 Aug 2026, Yuao Ma wrote:

> On Tue, Aug 4, 2026 at 1:00 AM Patrick Palka <[email protected]> wrote:
> >
> > On Mon, 3 Aug 2026, 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.
> >
> > Ah good point. Alternatively I think we could pass std::ref(__f)
> > in the recursive for_each calls.  I'm not sure which approach is
> > preferable.
> >
> 
> Indeed std::ref(__f) will work with stateful functor. I came up with
> another reason: the return value. The std::for_each returns __f
> whereas __for_each returns the _last, which might be useful for
> adoption by some algorithms.
> 
> > > 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.
> >
> > Makes sense.
> >
> > >
> > > > >        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
> > > > >
> > > >
> > >
> 
> 
> 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.

LGTM

> ---
>  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.
> +#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
> +
> +#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
>