Skip to content

Commit a5148ec

Browse files
committed
Optimize ranges::{for_each, for_each_n} for segmented iterators
1 parent 97a32f2 commit a5148ec

File tree

5 files changed

+108
-15
lines changed

5 files changed

+108
-15
lines changed

libcxx/include/__algorithm/ranges_for_each.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef _LIBCPP___ALGORITHM_RANGES_FOR_EACH_H
1010
#define _LIBCPP___ALGORITHM_RANGES_FOR_EACH_H
1111

12+
#include <__algorithm/for_each.h>
1213
#include <__algorithm/in_fun_result.h>
1314
#include <__config>
1415
#include <__functional/identity.h>
@@ -41,9 +42,16 @@ struct __for_each {
4142
template <class _Iter, class _Sent, class _Proj, class _Func>
4243
_LIBCPP_HIDE_FROM_ABI constexpr static for_each_result<_Iter, _Func>
4344
__for_each_impl(_Iter __first, _Sent __last, _Func& __func, _Proj& __proj) {
44-
for (; __first != __last; ++__first)
45-
std::invoke(__func, std::invoke(__proj, *__first));
46-
return {std::move(__first), std::move(__func)};
45+
if constexpr (random_access_iterator<_Iter> && sized_sentinel_for<_Sent, _Iter>) {
46+
auto __n = __last - __first;
47+
auto __end = __first + __n;
48+
std::for_each(__first, __end, [&](auto&& __val) { std::invoke(__func, std::invoke(__proj, __val)); });
49+
return {std::move(__end), std::move(__func)};
50+
} else {
51+
for (; __first != __last; ++__first)
52+
std::invoke(__func, std::invoke(__proj, *__first));
53+
return {std::move(__first), std::move(__func)};
54+
}
4755
}
4856

4957
public:

libcxx/include/__algorithm/ranges_for_each_n.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef _LIBCPP___ALGORITHM_RANGES_FOR_EACH_N_H
1010
#define _LIBCPP___ALGORITHM_RANGES_FOR_EACH_N_H
1111

12+
#include <__algorithm/for_each.h>
1213
#include <__algorithm/in_fun_result.h>
1314
#include <__config>
1415
#include <__functional/identity.h>
@@ -40,11 +41,17 @@ struct __for_each_n {
4041
template <input_iterator _Iter, class _Proj = identity, indirectly_unary_invocable<projected<_Iter, _Proj>> _Func>
4142
_LIBCPP_HIDE_FROM_ABI constexpr for_each_n_result<_Iter, _Func>
4243
operator()(_Iter __first, iter_difference_t<_Iter> __count, _Func __func, _Proj __proj = {}) const {
43-
while (__count-- > 0) {
44-
std::invoke(__func, std::invoke(__proj, *__first));
45-
++__first;
44+
if constexpr (random_access_iterator<_Iter>) {
45+
auto __last = __first + __count;
46+
std::for_each(__first, __last, [&](auto&& __val) { std::invoke(__func, std::invoke(__proj, __val)); });
47+
return {std::move(__last), std::move(__func)};
48+
} else {
49+
while (__count-- > 0) {
50+
std::invoke(__func, std::invoke(__proj, *__first));
51+
++__first;
52+
}
53+
return {std::move(__first), std::move(__func)};
4654
}
47-
return {std::move(__first), std::move(__func)};
4855
}
4956
};
5057

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
int main(int argc, char** argv) {
2222
auto std_for_each_n = [](auto first, auto n, auto f) { return std::for_each_n(first, n, f); };
2323

24-
// std::for_each_n
24+
// {std,ranges}::for_each_n
2525
{
2626
auto bm = []<class Container>(std::string name, auto for_each_n) {
2727
using ElemType = typename Container::value_type;

libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.pass.cpp

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020

2121
#include <algorithm>
2222
#include <array>
23+
#include <cassert>
24+
#include <deque>
2325
#include <ranges>
26+
#include <vector>
2427

2528
#include "almost_satisfies_types.h"
2629
#include "test_iterators.h"
@@ -30,7 +33,7 @@ struct Callable {
3033
};
3134

3235
template <class Iter, class Sent = Iter>
33-
concept HasForEachIt = requires (Iter iter, Sent sent) { std::ranges::for_each(iter, sent, Callable{}); };
36+
concept HasForEachIt = requires(Iter iter, Sent sent) { std::ranges::for_each(iter, sent, Callable{}); };
3437

3538
static_assert(HasForEachIt<int*>);
3639
static_assert(!HasForEachIt<InputIteratorNotDerivedFrom>);
@@ -47,7 +50,7 @@ static_assert(!HasForEachItFunc<IndirectUnaryPredicateNotPredicate>);
4750
static_assert(!HasForEachItFunc<IndirectUnaryPredicateNotCopyConstructible>);
4851

4952
template <class Range>
50-
concept HasForEachR = requires (Range range) { std::ranges::for_each(range, Callable{}); };
53+
concept HasForEachR = requires(Range range) { std::ranges::for_each(range, Callable{}); };
5154

5255
static_assert(HasForEachR<UncheckedRange<int*>>);
5356
static_assert(!HasForEachR<InputRangeNotDerivedFrom>);
@@ -68,7 +71,7 @@ constexpr void test_iterator() {
6871
{ // simple test
6972
{
7073
auto func = [i = 0](int& a) mutable { a += i++; };
71-
int a[] = {1, 6, 3, 4};
74+
int a[] = {1, 6, 3, 4};
7275
std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> decltype(auto) ret =
7376
std::ranges::for_each(Iter(a), Sent(Iter(a + 4)), func);
7477
assert(a[0] == 1);
@@ -81,8 +84,8 @@ constexpr void test_iterator() {
8184
assert(i == 4);
8285
}
8386
{
84-
auto func = [i = 0](int& a) mutable { a += i++; };
85-
int a[] = {1, 6, 3, 4};
87+
auto func = [i = 0](int& a) mutable { a += i++; };
88+
int a[] = {1, 6, 3, 4};
8689
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 4)));
8790
std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> decltype(auto) ret =
8891
std::ranges::for_each(range, func);
@@ -110,6 +113,30 @@ constexpr void test_iterator() {
110113
}
111114
}
112115

116+
struct deque_test {
117+
std::deque<int>* d_;
118+
int* i_;
119+
120+
deque_test(std::deque<int>& d, int& i) : d_(&d), i_(&i) {}
121+
122+
void operator()(int& v) {
123+
assert(&(*d_)[*i_] == &v);
124+
++*i_;
125+
}
126+
};
127+
128+
/*TEST_CONSTEXPR_CXX23*/
129+
void test_segmented_deque_iterator() { // TODO: Mark as TEST_CONSTEXPR_CXX23 once std::deque is constexpr
130+
// check that segmented iterators work properly
131+
int sizes[] = {0, 1, 2, 1023, 1024, 1025, 2047, 2048, 2049};
132+
for (const int size : sizes) {
133+
std::deque<int> d(size);
134+
int index = 0;
135+
136+
std::ranges::for_each(d, deque_test(d, index));
137+
}
138+
}
139+
113140
constexpr bool test() {
114141
test_iterator<cpp17_input_iterator<int*>, sentinel_wrapper<cpp17_input_iterator<int*>>>();
115142
test_iterator<cpp20_input_iterator<int*>, sentinel_wrapper<cpp20_input_iterator<int*>>>();
@@ -146,6 +173,15 @@ constexpr bool test() {
146173
}
147174
}
148175

176+
if (!TEST_IS_CONSTANT_EVALUATED) // TODO: Use TEST_STD_AT_LEAST_23_OR_RUNTIME_EVALUATED when std::deque is made constexpr
177+
test_segmented_deque_iterator();
178+
179+
{
180+
std::vector<std::vector<int>> vec = {{0}, {1, 2}, {3, 4, 5}, {6, 7, 8, 9}, {10}, {11, 12, 13}};
181+
auto v = vec | std::views::join;
182+
std::ranges::for_each(v, [i = 0](int x) mutable { assert(x == 2 * i++); }, [](int x) { return 2 * x; });
183+
}
184+
149185
return true;
150186
}
151187

libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each_n.pass.cpp

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717

1818
#include <algorithm>
1919
#include <array>
20+
#include <cassert>
21+
#include <deque>
22+
#include <iterator>
2023
#include <ranges>
24+
#include <ranges>
25+
#include <vector>
2126

2227
#include "almost_satisfies_types.h"
2328
#include "test_iterators.h"
@@ -27,7 +32,7 @@ struct Callable {
2732
};
2833

2934
template <class Iter>
30-
concept HasForEachN = requires (Iter iter) { std::ranges::for_each_n(iter, 0, Callable{}); };
35+
concept HasForEachN = requires(Iter iter) { std::ranges::for_each_n(iter, 0, Callable{}); };
3136

3237
static_assert(HasForEachN<int*>);
3338
static_assert(!HasForEachN<InputIteratorNotDerivedFrom>);
@@ -45,7 +50,7 @@ template <class Iter>
4550
constexpr void test_iterator() {
4651
{ // simple test
4752
auto func = [i = 0](int& a) mutable { a += i++; };
48-
int a[] = {1, 6, 3, 4};
53+
int a[] = {1, 6, 3, 4};
4954
std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> auto ret =
5055
std::ranges::for_each_n(Iter(a), 4, func);
5156
assert(a[0] == 1);
@@ -64,6 +69,30 @@ constexpr void test_iterator() {
6469
}
6570
}
6671

72+
struct deque_test {
73+
std::deque<int>* d_;
74+
int* i_;
75+
76+
deque_test(std::deque<int>& d, int& i) : d_(&d), i_(&i) {}
77+
78+
void operator()(int& v) {
79+
assert(&(*d_)[*i_] == &v);
80+
++*i_;
81+
}
82+
};
83+
84+
/*TEST_CONSTEXPR_CXX23*/
85+
void test_segmented_deque_iterator() { // TODO: Mark as TEST_CONSTEXPR_CXX23 once std::deque is constexpr
86+
// check that segmented iterators work properly
87+
int sizes[] = {0, 1, 2, 1023, 1024, 1025, 2047, 2048, 2049};
88+
for (const int size : sizes) {
89+
std::deque<int> d(size);
90+
int index = 0;
91+
92+
std::ranges::for_each_n(d.begin(), d.size(), deque_test(d, index));
93+
}
94+
}
95+
6796
constexpr bool test() {
6897
test_iterator<cpp17_input_iterator<int*>>();
6998
test_iterator<cpp20_input_iterator<int*>>();
@@ -89,6 +118,19 @@ constexpr bool test() {
89118
assert(a[2].other == 6);
90119
}
91120

121+
if (!TEST_IS_CONSTANT_EVALUATED) // TODO: Use TEST_STD_AT_LEAST_23_OR_RUNTIME_EVALUATED when std::deque is made constexpr
122+
test_segmented_deque_iterator();
123+
124+
{
125+
std::vector<std::vector<int>> vec = {{0}, {1, 2}, {3, 4, 5}, {6, 7, 8, 9}, {10}, {11, 12, 13}};
126+
auto v = vec | std::views::join;
127+
std::ranges::for_each_n(
128+
v.begin(),
129+
std::ranges::distance(v),
130+
[i = 0](int x) mutable { assert(x == 2 * i++); },
131+
[](int x) { return 2 * x; });
132+
}
133+
92134
return true;
93135
}
94136

0 commit comments

Comments
 (0)