Skip to content

Commit f5d13ab

Browse files
committed
Fix review comments
1 parent c9cd33d commit f5d13ab

File tree

10 files changed

+164
-206
lines changed

10 files changed

+164
-206
lines changed

libcxx/docs/ReleaseNotes/21.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ Improvements and New Features
6161
- Updated formatting library to Unicode 16.0.0.
6262

6363
- The ``std::ranges::for_each`` and ``std::ranges::for_each_n`` algorithms have been optimized for segmented iterators,
64-
resulting in performance improvements of up to 21.3x for ``std::deque::iterator`` segmented inputs and 24.9x for
65-
``join_view`` of ``vector<vector<T>>``.
64+
resulting in performance improvements of up to 21.3x for ``std::deque::iterator`` and 24.9x for ``join_view`` of
65+
``vector<vector<char>>``.
6666

6767
Deprecations and Removals
6868
-------------------------

libcxx/include/__algorithm/for_each.h

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#include <__algorithm/for_each_segment.h>
1414
#include <__config>
15+
#include <__functional/identity.h>
16+
#include <__functional/invoke.h>
1517
#include <__iterator/segmented_iterator.h>
1618
#include <__type_traits/enable_if.h>
1719
#include <__utility/move.h>
@@ -25,44 +27,49 @@ _LIBCPP_PUSH_MACROS
2527

2628
_LIBCPP_BEGIN_NAMESPACE_STD
2729

28-
template <class _InputIterator, class _Sent, class _Function>
29-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Function
30-
__for_each(_InputIterator __first, _Sent __last, _Function& __f) {
30+
template <class _InputIterator, class _Sent, class _Function, class _Proj>
31+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
32+
__for_each(_InputIterator __first, _Sent __last, _Function& __f, _Proj& __proj) {
3133
for (; __first != __last; ++__first)
32-
__f(*__first);
33-
return std::move(__f);
34+
std::invoke(__f, std::invoke(__proj, *__first));
35+
return std::move(__first);
3436
}
3537

3638
// __segment_processor handles the per-segment processing by applying the user-provided function to each element
3739
// within the segment. It acts as a functor passed to the segmented iterator algorithm __for_each_segment.
38-
template <class _SegmentedIterator, class _Function>
40+
template <class _SegmentedIterator, class _Function, class _Proj>
3941
struct __segment_processor {
4042
using _Traits _LIBCPP_NODEBUG = __segmented_iterator_traits<_SegmentedIterator>;
4143

4244
_Function& __func_;
45+
_Proj& __proj_;
4346

44-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 explicit __segment_processor(_Function& __func)
45-
: __func_(__func) {}
47+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 explicit __segment_processor(_Function& __func, _Proj& __proj)
48+
: __func_(__func), __proj_(__proj) {}
4649

4750
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
4851
operator()(typename _Traits::__local_iterator __lfirst, typename _Traits::__local_iterator __llast) {
49-
std::__for_each(__lfirst, __llast, __func_);
52+
std::__for_each(__lfirst, __llast, __func_, __proj_);
5053
}
5154
};
5255

5356
template <class _SegmentedIterator,
5457
class _Function,
58+
class _Proj,
5559
__enable_if_t<__is_segmented_iterator<_SegmentedIterator>::value, int> = 0>
56-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Function
57-
__for_each(_SegmentedIterator __first, _SegmentedIterator __last, _Function& __func) {
58-
std::__for_each_segment(__first, __last, std::__segment_processor<_SegmentedIterator, _Function>(__func));
59-
return std::move(__func);
60+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _SegmentedIterator
61+
__for_each(_SegmentedIterator __first, _SegmentedIterator __last, _Function& __func, _Proj& __proj) {
62+
std::__for_each_segment(
63+
__first, __last, std::__segment_processor<_SegmentedIterator, _Function, _Proj>(__func, __proj));
64+
return std::move(__last);
6065
}
6166

6267
template <class _InputIterator, class _Function>
6368
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Function
6469
for_each(_InputIterator __first, _InputIterator __last, _Function __f) {
65-
return std::__for_each(__first, __last, __f);
70+
__identity __proj;
71+
std::__for_each(__first, __last, __f, __proj);
72+
return std::move(__f);
6673
}
6774

6875
_LIBCPP_END_NAMESPACE_STD

libcxx/include/__algorithm/for_each_n.h

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
#include <__algorithm/for_each.h>
1414
#include <__algorithm/for_each_n_segment.h>
1515
#include <__config>
16+
#include <__functional/identity.h>
17+
#include <__functional/invoke.h>
1618
#include <__iterator/iterator_traits.h>
17-
#include <__iterator/next.h>
1819
#include <__iterator/segmented_iterator.h>
1920
#include <__type_traits/enable_if.h>
2021
#include <__utility/convert_to_integral.h>
@@ -25,35 +26,63 @@
2526

2627
_LIBCPP_BEGIN_NAMESPACE_STD
2728

28-
#if _LIBCPP_STD_VER >= 17
29-
3029
template <class _InputIterator,
3130
class _Size,
3231
class _Function,
33-
__enable_if_t<!__is_segmented_iterator<_InputIterator>::value ||
34-
__has_exactly_input_iterator_category<_InputIterator>::value,
32+
class _Proj,
33+
__enable_if_t<!__has_random_access_iterator_category<_InputIterator>::value &&
34+
(!__is_segmented_iterator<_InputIterator>::value
35+
// || !__has_random_access_iterator_category<
36+
// typename __segmented_iterator_traits<_InputIterator>::__local_iterator>::value
37+
), // TODO: __segmented_iterator_traits<_InputIterator> results in template instantiation
38+
// during SFINAE, which is a hard error to be fixed. Once fixed, we should uncomment.
3539
int> = 0>
3640
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
37-
for_each_n(_InputIterator __first, _Size __orig_n, _Function __f) {
41+
__for_each_n(_InputIterator __first, _Size __orig_n, _Function& __f, _Proj& __proj) {
3842
typedef decltype(std::__convert_to_integral(__orig_n)) _IntegralSize;
3943
_IntegralSize __n = __orig_n;
4044
while (__n > 0) {
41-
__f(*__first);
45+
std::invoke(__f, std::invoke(__proj, *__first));
4246
++__first;
4347
--__n;
4448
}
4549
return __first;
4650
}
4751

52+
template <class _RandIter,
53+
class _Size,
54+
class _Function,
55+
class _Proj,
56+
__enable_if_t<__has_random_access_iterator_category<_RandIter>::value, int> = 0>
57+
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _RandIter
58+
__for_each_n(_RandIter __first, _Size __orig_n, _Function& __f, _Proj& __proj) {
59+
typedef decltype(std::__convert_to_integral(__orig_n)) _IntegralSize;
60+
_IntegralSize __n = __orig_n;
61+
return std::__for_each(__first, __first + __n, __f, __proj);
62+
}
63+
4864
template <class _SegmentedIterator,
4965
class _Size,
5066
class _Function,
51-
__enable_if_t<__is_segmented_iterator<_SegmentedIterator>::value &&
52-
__has_forward_iterator_category<_SegmentedIterator>::value,
67+
class _Proj,
68+
__enable_if_t<!__has_random_access_iterator_category<_SegmentedIterator>::value &&
69+
__is_segmented_iterator<_SegmentedIterator>::value &&
70+
__has_random_access_iterator_category<
71+
typename __segmented_iterator_traits<_SegmentedIterator>::__local_iterator>::value,
5372
int> = 0>
5473
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _SegmentedIterator
55-
for_each_n(_SegmentedIterator __first, _Size __orig_n, _Function __f) {
56-
return std::__for_each_n_segment(__first, __orig_n, std::__segment_processor<_SegmentedIterator, _Function>(__f));
74+
__for_each_n(_SegmentedIterator __first, _Size __orig_n, _Function& __f, _Proj& __proj) {
75+
return std::__for_each_n_segment(
76+
__first, __orig_n, std::__segment_processor<_SegmentedIterator, _Function, _Proj>(__f, __proj));
77+
}
78+
79+
#if _LIBCPP_STD_VER >= 17
80+
81+
template <class _InputIterator, class _Size, class _Function>
82+
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
83+
for_each_n(_InputIterator __first, _Size __orig_n, _Function __f) {
84+
__identity __proj;
85+
return std::__for_each_n(__first, __orig_n, __f, __proj);
5786
}
5887

5988
#endif

libcxx/include/__algorithm/for_each_n_segment.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include <__config>
1313
#include <__iterator/distance.h>
14+
#include <__iterator/iterator_traits.h>
1415
#include <__iterator/next.h>
1516
#include <__iterator/segmented_iterator.h>
1617
#include <__utility/convert_to_integral.h>
@@ -22,14 +23,18 @@
2223
_LIBCPP_BEGIN_NAMESPACE_STD
2324

2425
// __for_each_n_segment optimizes linear iteration over segmented iterators. It processes a segmented
25-
// input range defined by (__first, __orig_n), where __first is the starting segmented iterator and
26-
// __orig_n is the number of elements to process. The functor __func is applied to each segment using
26+
// input range defined by [__first, __first + __n), where __first is the starting segmented iterator
27+
// and __n is the number of elements to process. The functor __func is applied to each segment using
2728
// local iterator pairs for that segment. The return value of __func is ignored, and the function
2829
// returns an iterator pointing to one past the last processed element in the input range.
2930

3031
template <class _SegmentedIterator, class _Size, class _Functor>
3132
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _SegmentedIterator
3233
__for_each_n_segment(_SegmentedIterator __first, _Size __orig_n, _Functor __func) {
34+
static_assert(__is_segmented_iterator<_SegmentedIterator>::value &&
35+
__has_random_access_iterator_category<
36+
typename __segmented_iterator_traits<_SegmentedIterator>::__local_iterator>::value,
37+
"__for_each_n_segment only works with segmented iterators with random-access local iterators");
3338
if (__orig_n == 0)
3439
return __first;
3540

libcxx/include/__algorithm/ranges_for_each.h

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,13 @@ struct __for_each {
4444
template <class _Iter, class _Sent, class _Proj, class _Func>
4545
_LIBCPP_HIDE_FROM_ABI constexpr static for_each_result<_Iter, _Func>
4646
__for_each_impl(_Iter __first, _Sent __last, _Func& __func, _Proj& __proj) {
47-
if constexpr (std::assignable_from<_Iter&, _Sent>) {
48-
_Iter __end = std::move(__last);
49-
std::for_each(__first, __end, [&](auto&& __val) { std::invoke(__func, std::invoke(__proj, __val)); });
50-
return {std::move(__end), std::move(__func)};
51-
} else if constexpr (sized_sentinel_for<_Sent, _Iter>) {
52-
auto __end = std::for_each_n(__first, __last - __first, [&](auto&& __val) {
53-
std::invoke(__func, std::invoke(__proj, __val));
54-
});
47+
if constexpr (!std::assignable_from<_Iter&, _Sent> && sized_sentinel_for<_Sent, _Iter>) {
48+
auto __n = __last - __first;
49+
auto __end = std::__for_each_n(std::move(__first), __n, __func, __proj);
5550
return {std::move(__end), std::move(__func)};
5651
} else {
57-
for (; __first != __last; ++__first)
58-
std::invoke(__func, std::invoke(__proj, *__first));
59-
return {std::move(__first), std::move(__func)};
52+
auto __end = std::__for_each(std::move(__first), std::move(__last), __func, __proj);
53+
return {std::move(__end), std::move(__func)};
6054
}
6155
}
6256

libcxx/include/__algorithm/ranges_for_each_n.h

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
#include <__iterator/concepts.h>
1818
#include <__iterator/incrementable_traits.h>
1919
#include <__iterator/iterator_traits.h>
20-
#include <__iterator/next.h>
2120
#include <__iterator/projected.h>
2221
#include <__ranges/concepts.h>
2322
#include <__utility/move.h>
@@ -42,17 +41,8 @@ struct __for_each_n {
4241
template <input_iterator _Iter, class _Proj = identity, indirectly_unary_invocable<projected<_Iter, _Proj>> _Func>
4342
_LIBCPP_HIDE_FROM_ABI constexpr for_each_n_result<_Iter, _Func>
4443
operator()(_Iter __first, iter_difference_t<_Iter> __count, _Func __func, _Proj __proj = {}) const {
45-
if constexpr (forward_iterator<_Iter>) {
46-
auto __f = [&](auto&& __val) { std::invoke(__func, std::invoke(__proj, __val)); };
47-
auto __last = std::for_each_n(__first, __count, __f);
48-
return {std::move(__last), std::move(__func)};
49-
} else {
50-
while (__count-- > 0) {
51-
std::invoke(__func, std::invoke(__proj, *__first));
52-
++__first;
53-
}
54-
return {std::move(__first), std::move(__func)};
55-
}
44+
auto __last = std::__for_each_n(std::move(__first), __count, __func, __proj);
45+
return {std::move(__last), std::move(__func)};
5646
}
5747
};
5848

libcxx/test/benchmarks/algorithms/nonmodifying/for_each.bench.cpp

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <cstddef>
1313
#include <deque>
1414
#include <list>
15+
#include <ranges>
1516
#include <string>
1617
#include <vector>
1718

@@ -48,20 +49,6 @@ int main(int argc, char** argv) {
4849
->Arg(1 << 16)
4950
->Arg(1 << 18);
5051
};
51-
bm.operator()<std::vector<char>>("std::for_each(vector<char>)", std_for_each);
52-
bm.operator()<std::deque<char>>("std::for_each(deque<char>)", std_for_each);
53-
bm.operator()<std::list<char>>("std::for_each(list<char>)", std_for_each);
54-
bm.operator()<std::vector<char>>("rng::for_each(vector<char>)", std::ranges::for_each);
55-
bm.operator()<std::deque<char>>("rng::for_each(deque<char>)", std::ranges::for_each);
56-
bm.operator()<std::list<char>>("rng::for_each(list<char>)", std::ranges::for_each);
57-
58-
bm.operator()<std::vector<short>>("std::for_each(vector<short>)", std_for_each);
59-
bm.operator()<std::deque<short>>("std::for_each(deque<short>)", std_for_each);
60-
bm.operator()<std::list<short>>("std::for_each(list<short>)", std_for_each);
61-
bm.operator()<std::vector<short>>("rng::for_each(vector<short>)", std::ranges::for_each);
62-
bm.operator()<std::deque<short>>("rng::for_each(deque<short>)", std::ranges::for_each);
63-
bm.operator()<std::list<short>>("rng::for_each(list<short>)", std::ranges::for_each);
64-
6552
bm.operator()<std::vector<int>>("std::for_each(vector<int>)", std_for_each);
6653
bm.operator()<std::deque<int>>("std::for_each(deque<int>)", std_for_each);
6754
bm.operator()<std::list<int>>("std::for_each(list<int>)", std_for_each);
@@ -70,6 +57,47 @@ int main(int argc, char** argv) {
7057
bm.operator()<std::list<int>>("rng::for_each(list<int>)", std::ranges::for_each);
7158
}
7259

60+
// {std,ranges}::for_each for join_view
61+
{
62+
auto bm = []<class Container>(std::string name, auto for_each) {
63+
using C1 = typename Container::value_type;
64+
using ElemType = typename C1::value_type;
65+
66+
benchmark::RegisterBenchmark(
67+
name,
68+
[for_each](auto& st) {
69+
std::size_t const size = st.range(0);
70+
std::size_t const seg_size = 256;
71+
std::size_t const segments = (size + seg_size - 1) / seg_size;
72+
Container c(segments);
73+
for (std::size_t i = 0, n = size; i < segments; ++i, n -= seg_size) {
74+
c[i].resize(std::min(seg_size, n), ElemType(1));
75+
}
76+
77+
auto view = c | std::views::join;
78+
auto first = view.begin();
79+
auto last = view.end();
80+
81+
for ([[maybe_unused]] auto _ : st) {
82+
benchmark::DoNotOptimize(c);
83+
auto result = for_each(first, last, [](ElemType& x) { x = std::clamp<ElemType>(x, 10, 100); });
84+
benchmark::DoNotOptimize(result);
85+
}
86+
})
87+
->Arg(8)
88+
->Arg(32)
89+
->Arg(50) // non power-of-two
90+
->Arg(1024)
91+
->Arg(4096)
92+
->Arg(8192)
93+
->Arg(1 << 14)
94+
->Arg(1 << 16)
95+
->Arg(1 << 18);
96+
};
97+
bm.operator()<std::vector<std::vector<int>>>("std::for_each(join_view(vector<vector<int>>))", std_for_each);
98+
bm.operator()<std::vector<std::vector<int>>>("rng::for_each(join_view(vector<vector<int>>)", std::ranges::for_each);
99+
}
100+
73101
benchmark::Initialize(&argc, argv);
74102
benchmark::RunSpecifiedBenchmarks();
75103
benchmark::Shutdown();

0 commit comments

Comments
 (0)