Skip to content

Commit 252941e

Browse files
committed
Fix benchmarks for contains_subrange and find_end
1 parent 76d15cc commit 252941e

File tree

2 files changed

+250
-65
lines changed

2 files changed

+250
-65
lines changed

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

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
1010

1111
#include <algorithm>
12+
#include <cassert>
1213
#include <cstddef>
1314
#include <deque>
1415
#include <iterator>
@@ -19,7 +20,7 @@
1920
#include "../../GenerateInput.h"
2021

2122
int main(int argc, char** argv) {
22-
// Benchmark ranges::contains_subrange where we never find our target, which is the
23+
// Benchmark ranges::contains_subrange where we never find the needle, which is the
2324
// worst case.
2425
{
2526
auto bm = []<class Container>(std::string name) {
@@ -30,13 +31,15 @@ int main(int argc, char** argv) {
3031
using ValueType = typename Container::value_type;
3132
ValueType x = Generate<ValueType>::random();
3233
ValueType y = random_different_from({x});
33-
Container c(size, x);
34-
Container subrange(size / 10, y); // subrange of length 10% of the full range, but we'll never find it
34+
Container haystack(size, x);
35+
std::size_t n = size / 10; // needle size is 10% of the haystack, but we'll never find it
36+
assert(n > 0);
37+
Container needle(n, y);
3538

3639
for (auto _ : st) {
37-
benchmark::DoNotOptimize(c);
38-
benchmark::DoNotOptimize(subrange);
39-
auto result = std::ranges::contains_subrange(c, subrange);
40+
benchmark::DoNotOptimize(haystack);
41+
benchmark::DoNotOptimize(needle);
42+
auto result = std::ranges::contains_subrange(haystack, needle);
4043
benchmark::DoNotOptimize(result);
4144
}
4245
})
@@ -51,6 +54,107 @@ int main(int argc, char** argv) {
5154
bm.operator()<std::list<int>>("rng::contains_subrange(list<int>) (process all)");
5255
}
5356

57+
// Benchmark ranges::contains_subrange where we intersperse "near matches" inside the haystack.
58+
{
59+
auto bm = []<class Container>(std::string name) {
60+
benchmark::RegisterBenchmark(
61+
name,
62+
[](auto& st) {
63+
std::size_t const size = st.range(0);
64+
using ValueType = typename Container::value_type;
65+
ValueType x = Generate<ValueType>::random();
66+
ValueType y = random_different_from({x});
67+
Container haystack(size, x);
68+
std::size_t n = size / 10; // needle size is 10% of the haystack, but we'll never find it
69+
assert(n > 0);
70+
Container needle(n, y);
71+
72+
// intersperse near-matches inside the haystack
73+
{
74+
auto first = haystack.begin();
75+
for (int i = 0; i != 10; ++i) {
76+
first = std::copy_n(needle.begin(), n - 1, first);
77+
++first; // this causes the subsequence not to match because it has length n-1
78+
}
79+
}
80+
81+
for ([[maybe_unused]] auto _ : st) {
82+
benchmark::DoNotOptimize(haystack);
83+
benchmark::DoNotOptimize(needle);
84+
auto result = std::ranges::contains_subrange(haystack, needle);
85+
benchmark::DoNotOptimize(result);
86+
}
87+
})
88+
->Arg(1000) // non power-of-two
89+
->Arg(1024)
90+
->Arg(8192);
91+
};
92+
bm.operator()<std::vector<int>>("rng::contains_subrange(vector<int>) (near matches)");
93+
bm.operator()<std::deque<int>>("rng::contains_subrange(deque<int>) (near matches)");
94+
bm.operator()<std::list<int>>("rng::contains_subrange(list<int>) (near matches)");
95+
}
96+
97+
// Special case: the two ranges are the same length (and they are equal, which is the worst case).
98+
{
99+
auto bm = []<class Container>(std::string name) {
100+
benchmark::RegisterBenchmark(
101+
name,
102+
[](auto& st) {
103+
std::size_t const size = st.range(0);
104+
using ValueType = typename Container::value_type;
105+
ValueType x = Generate<ValueType>::random();
106+
Container haystack(size, x);
107+
Container needle(size, x);
108+
109+
for (auto _ : st) {
110+
benchmark::DoNotOptimize(haystack);
111+
benchmark::DoNotOptimize(needle);
112+
auto result = std::ranges::contains_subrange(haystack, needle);
113+
benchmark::DoNotOptimize(result);
114+
}
115+
})
116+
->Arg(16)
117+
->Arg(32)
118+
->Arg(50) // non power-of-two
119+
->Arg(8192)
120+
->Arg(1 << 20);
121+
};
122+
bm.operator()<std::vector<int>>("rng::contains_subrange(vector<int>) (same length)");
123+
bm.operator()<std::deque<int>>("rng::contains_subrange(deque<int>) (same length)");
124+
bm.operator()<std::list<int>>("rng::contains_subrange(list<int>) (same length)");
125+
}
126+
127+
// Special case: the needle contains a single element (which we never find, i.e. the worst case).
128+
{
129+
auto bm = []<class Container>(std::string name) {
130+
benchmark::RegisterBenchmark(
131+
name,
132+
[](auto& st) {
133+
std::size_t const size = st.range(0);
134+
using ValueType = typename Container::value_type;
135+
ValueType x = Generate<ValueType>::random();
136+
ValueType y = random_different_from({x});
137+
Container haystack(size, x);
138+
Container needle(1, y);
139+
140+
for (auto _ : st) {
141+
benchmark::DoNotOptimize(haystack);
142+
benchmark::DoNotOptimize(needle);
143+
auto result = std::ranges::contains_subrange(haystack, needle);
144+
benchmark::DoNotOptimize(result);
145+
}
146+
})
147+
->Arg(16)
148+
->Arg(32)
149+
->Arg(50) // non power-of-two
150+
->Arg(8192)
151+
->Arg(1 << 20);
152+
};
153+
bm.operator()<std::vector<int>>("rng::contains_subrange(vector<int>) (single element)");
154+
bm.operator()<std::deque<int>>("rng::contains_subrange(deque<int>) (single element)");
155+
bm.operator()<std::list<int>>("rng::contains_subrange(list<int>) (single element)");
156+
}
157+
54158
benchmark::Initialize(&argc, argv);
55159
benchmark::RunSpecifiedBenchmarks();
56160
benchmark::Shutdown();

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

Lines changed: 140 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// UNSUPPORTED: c++03, c++11, c++14, c++17
1010

1111
#include <algorithm>
12+
#include <cassert>
1213
#include <cstddef>
1314
#include <deque>
1415
#include <forward_list>
@@ -38,8 +39,34 @@ int main(int argc, char** argv) {
3839
});
3940
};
4041

41-
// Benchmark {std,ranges}::find_end where the subsequence is found
42-
// 25% into the sequence
42+
auto register_benchmarks = [&](auto bm, std::string comment) {
43+
// {std,ranges}::find_end(it1, it1, it2, it2)
44+
bm.template operator()<std::vector<int>>("std::find_end(vector<int>) (" + comment + ")", std_find_end);
45+
bm.template operator()<std::deque<int>>("std::find_end(deque<int>) (" + comment + ")", std_find_end);
46+
bm.template operator()<std::list<int>>("std::find_end(list<int>) (" + comment + ")", std_find_end);
47+
bm.template operator()<std::forward_list<int>>("std::find_end(forward_list<int>) (" + comment + ")", std_find_end);
48+
bm.template operator()<std::vector<int>>("rng::find_end(vector<int>) (" + comment + ")", std::ranges::find_end);
49+
bm.template operator()<std::deque<int>>("rng::find_end(deque<int>) (" + comment + ")", std::ranges::find_end);
50+
bm.template operator()<std::list<int>>("rng::find_end(list<int>) (" + comment + ")", std::ranges::find_end);
51+
bm.template operator()<std::forward_list<int>>(
52+
"rng::find_end(forward_list<int>) (" + comment + ")", std::ranges::find_end);
53+
54+
// {std,ranges}::find_end(it1, it1, it2, it2, pred)
55+
bm.template operator()<std::vector<int>>("std::find_end(vector<int>, pred) (" + comment + ")", std_find_end_pred);
56+
bm.template operator()<std::deque<int>>("std::find_end(deque<int>, pred) (" + comment + ")", std_find_end_pred);
57+
bm.template operator()<std::list<int>>("std::find_end(list<int>, pred) (" + comment + ")", std_find_end_pred);
58+
bm.template operator()<std::forward_list<int>>(
59+
"std::find_end(forward_list<int>, pred) (" + comment + ")", std_find_end_pred);
60+
bm.template operator()<std::vector<int>>(
61+
"rng::find_end(vector<int>, pred) (" + comment + ")", ranges_find_end_pred);
62+
bm.template operator()<std::deque<int>>("rng::find_end(deque<int>, pred) (" + comment + ")", ranges_find_end_pred);
63+
bm.template operator()<std::list<int>>("rng::find_end(list<int>, pred) (" + comment + ")", ranges_find_end_pred);
64+
bm.template operator()<std::forward_list<int>>(
65+
"rng::find_end(forward_list<int>, pred) (" + comment + ")", ranges_find_end_pred);
66+
};
67+
68+
// Benchmark {std,ranges}::find_end where we never find the needle, which is the
69+
// worst case.
4370
{
4471
auto bm = []<class Container>(std::string name, auto find_end) {
4572
benchmark::RegisterBenchmark(
@@ -49,16 +76,15 @@ int main(int argc, char** argv) {
4976
using ValueType = typename Container::value_type;
5077
ValueType x = Generate<ValueType>::random();
5178
ValueType y = random_different_from({x});
52-
Container c(size, x);
53-
Container subrange(size / 10, y); // subrange of length 10% of the full range
79+
Container haystack(size, x);
80+
std::size_t n = size / 10; // needle size is 10% of the haystack, but we'll never find it
81+
assert(n > 0);
82+
Container needle(n, y);
5483

55-
// put the element we're searching for at 25% of the sequence
56-
std::ranges::copy(subrange, std::next(c.begin(), size / 4));
57-
58-
for ([[maybe_unused]] auto _ : st) {
59-
benchmark::DoNotOptimize(c);
60-
benchmark::DoNotOptimize(subrange);
61-
auto result = find_end(c.begin(), c.end(), subrange.begin(), subrange.end());
84+
for (auto _ : st) {
85+
benchmark::DoNotOptimize(haystack);
86+
benchmark::DoNotOptimize(needle);
87+
auto result = find_end(haystack.begin(), haystack.end(), needle.begin(), needle.end());
6288
benchmark::DoNotOptimize(result);
6389
}
6490
})
@@ -67,29 +93,10 @@ int main(int argc, char** argv) {
6793
->Arg(8192)
6894
->Arg(1 << 20);
6995
};
70-
// {std,ranges}::find_end(it1, it1, it2, it2)
71-
bm.operator()<std::vector<int>>("std::find_end(vector<int>) (bail 25%)", std_find_end);
72-
bm.operator()<std::deque<int>>("std::find_end(deque<int>) (bail 25%)", std_find_end);
73-
bm.operator()<std::list<int>>("std::find_end(list<int>) (bail 25%)", std_find_end);
74-
bm.operator()<std::forward_list<int>>("std::find_end(forward_list<int>) (bail 25%)", std_find_end);
75-
bm.operator()<std::vector<int>>("rng::find_end(vector<int>) (bail 25%)", std::ranges::find_end);
76-
bm.operator()<std::deque<int>>("rng::find_end(deque<int>) (bail 25%)", std::ranges::find_end);
77-
bm.operator()<std::list<int>>("rng::find_end(list<int>) (bail 25%)", std::ranges::find_end);
78-
bm.operator()<std::forward_list<int>>("rng::find_end(forward_list<int>) (bail 25%)", std::ranges::find_end);
79-
80-
// {std,ranges}::find_end(it1, it1, it2, it2, pred)
81-
bm.operator()<std::vector<int>>("std::find_end(vector<int>, pred) (bail 25%)", std_find_end_pred);
82-
bm.operator()<std::deque<int>>("std::find_end(deque<int>, pred) (bail 25%)", std_find_end_pred);
83-
bm.operator()<std::list<int>>("std::find_end(list<int>, pred) (bail 25%)", std_find_end_pred);
84-
bm.operator()<std::forward_list<int>>("std::find_end(forward_list<int>, pred) (bail 25%)", std_find_end_pred);
85-
bm.operator()<std::vector<int>>("rng::find_end(vector<int>, pred) (bail 25%)", ranges_find_end_pred);
86-
bm.operator()<std::deque<int>>("rng::find_end(deque<int>, pred) (bail 25%)", ranges_find_end_pred);
87-
bm.operator()<std::list<int>>("rng::find_end(list<int>, pred) (bail 25%)", ranges_find_end_pred);
88-
bm.operator()<std::forward_list<int>>("rng::find_end(forward_list<int>, pred) (bail 25%)", ranges_find_end_pred);
96+
register_benchmarks(bm, "process all");
8997
}
9098

91-
// Benchmark {std,ranges}::find_end where the subsequence is found
92-
// 90% into the sequence (i.e. near the end)
99+
// Benchmark {std,ranges}::find_end where we intersperse "near matches" inside the haystack.
93100
{
94101
auto bm = []<class Container>(std::string name, auto find_end) {
95102
benchmark::RegisterBenchmark(
@@ -99,43 +106,117 @@ int main(int argc, char** argv) {
99106
using ValueType = typename Container::value_type;
100107
ValueType x = Generate<ValueType>::random();
101108
ValueType y = random_different_from({x});
102-
Container c(size, x);
103-
Container subrange(size / 10, y); // subrange of length 10% of the full range
109+
Container haystack(size, x);
110+
std::size_t n = size / 10; // needle size is 10% of the haystack
111+
assert(n > 0);
112+
Container needle(n, y);
104113

105-
// put the element we're searching for at 90% of the sequence
106-
std::ranges::copy(subrange, std::next(c.begin(), (9 * size) / 10));
114+
// intersperse near-matches inside the haystack
115+
{
116+
auto first = haystack.begin();
117+
for (int i = 0; i != 10; ++i) {
118+
first = std::copy_n(needle.begin(), n - 1, first);
119+
++first; // this causes the subsequence not to match because it has length n-1
120+
}
121+
}
107122

108123
for ([[maybe_unused]] auto _ : st) {
109-
benchmark::DoNotOptimize(c);
110-
benchmark::DoNotOptimize(subrange);
111-
auto result = find_end(c.begin(), c.end(), subrange.begin(), subrange.end());
124+
benchmark::DoNotOptimize(haystack);
125+
benchmark::DoNotOptimize(needle);
126+
auto result = find_end(haystack.begin(), haystack.end(), needle.begin(), needle.end());
112127
benchmark::DoNotOptimize(result);
113128
}
114129
})
115130
->Arg(1000) // non power-of-two
116131
->Arg(1024)
117-
->Arg(8192)
118-
->Arg(1 << 20);
132+
->Arg(8192);
119133
};
120-
// {std,ranges}::find_end(it1, it1, it2, it2)
121-
bm.operator()<std::vector<int>>("std::find_end(vector<int>) (bail 90%)", std_find_end);
122-
bm.operator()<std::deque<int>>("std::find_end(deque<int>) (bail 90%)", std_find_end);
123-
bm.operator()<std::list<int>>("std::find_end(list<int>) (bail 90%)", std_find_end);
124-
bm.operator()<std::forward_list<int>>("std::find_end(forward_list<int>) (bail 90%)", std_find_end);
125-
bm.operator()<std::vector<int>>("rng::find_end(vector<int>) (bail 90%)", std::ranges::find_end);
126-
bm.operator()<std::deque<int>>("rng::find_end(deque<int>) (bail 90%)", std::ranges::find_end);
127-
bm.operator()<std::list<int>>("rng::find_end(list<int>) (bail 90%)", std::ranges::find_end);
128-
bm.operator()<std::forward_list<int>>("rng::find_end(forward_list<int>) (bail 90%)", std::ranges::find_end);
134+
register_benchmarks(bm, "near matches");
135+
}
129136

130-
// {std,ranges}::find_end(it1, it1, it2, it2, pred)
131-
bm.operator()<std::vector<int>>("std::find_end(vector<int>, pred) (bail 90%)", std_find_end_pred);
132-
bm.operator()<std::deque<int>>("std::find_end(deque<int>, pred) (bail 90%)", std_find_end_pred);
133-
bm.operator()<std::list<int>>("std::find_end(list<int>, pred) (bail 90%)", std_find_end_pred);
134-
bm.operator()<std::forward_list<int>>("std::find_end(forward_list<int>, pred) (bail 90%)", std_find_end_pred);
135-
bm.operator()<std::vector<int>>("rng::find_end(vector<int>, pred) (bail 90%)", ranges_find_end_pred);
136-
bm.operator()<std::deque<int>>("rng::find_end(deque<int>, pred) (bail 90%)", ranges_find_end_pred);
137-
bm.operator()<std::list<int>>("rng::find_end(list<int>, pred) (bail 90%)", ranges_find_end_pred);
138-
bm.operator()<std::forward_list<int>>("rng::find_end(forward_list<int>, pred) (bail 90%)", ranges_find_end_pred);
137+
// Special case: the two ranges are the same length (and they are equal, which is the worst case).
138+
{
139+
auto bm = []<class Container>(std::string name, auto find_end) {
140+
benchmark::RegisterBenchmark(
141+
name,
142+
[find_end](auto& st) {
143+
std::size_t const size = st.range(0);
144+
using ValueType = typename Container::value_type;
145+
ValueType x = Generate<ValueType>::random();
146+
Container haystack(size, x);
147+
Container needle(size, x);
148+
149+
for (auto _ : st) {
150+
benchmark::DoNotOptimize(haystack);
151+
benchmark::DoNotOptimize(needle);
152+
auto result = find_end(haystack.begin(), haystack.end(), needle.begin(), needle.end());
153+
benchmark::DoNotOptimize(result);
154+
}
155+
})
156+
->Arg(1000) // non power-of-two
157+
->Arg(1024)
158+
->Arg(8192);
159+
};
160+
register_benchmarks(bm, "same length");
161+
}
162+
163+
// Special case: the needle contains a single element (which we never find, i.e. the worst case).
164+
{
165+
auto bm = []<class Container>(std::string name, auto find_end) {
166+
benchmark::RegisterBenchmark(
167+
name,
168+
[find_end](auto& st) {
169+
std::size_t const size = st.range(0);
170+
using ValueType = typename Container::value_type;
171+
ValueType x = Generate<ValueType>::random();
172+
ValueType y = random_different_from({x});
173+
Container haystack(size, x);
174+
Container needle(1, y);
175+
176+
for (auto _ : st) {
177+
benchmark::DoNotOptimize(haystack);
178+
benchmark::DoNotOptimize(needle);
179+
auto result = find_end(haystack.begin(), haystack.end(), needle.begin(), needle.end());
180+
benchmark::DoNotOptimize(result);
181+
}
182+
})
183+
->Arg(1000) // non power-of-two
184+
->Arg(1024)
185+
->Arg(8192);
186+
};
187+
register_benchmarks(bm, "single element");
188+
}
189+
190+
// Special case: we have a match close to the end of the haystack (ideal case if we start searching from the end).
191+
{
192+
auto bm = []<class Container>(std::string name, auto find_end) {
193+
benchmark::RegisterBenchmark(
194+
name,
195+
[find_end](auto& st) {
196+
std::size_t const size = st.range(0);
197+
using ValueType = typename Container::value_type;
198+
ValueType x = Generate<ValueType>::random();
199+
ValueType y = random_different_from({x});
200+
Container haystack(size, x);
201+
std::size_t n = size / 10; // needle size is 10% of the haystack
202+
assert(n > 0);
203+
Container needle(n, y);
204+
205+
// put the needle at 90% of the haystack
206+
std::ranges::copy(needle, std::next(haystack.begin(), (9 * size) / 10));
207+
208+
for (auto _ : st) {
209+
benchmark::DoNotOptimize(haystack);
210+
benchmark::DoNotOptimize(needle);
211+
auto result = find_end(haystack.begin(), haystack.end(), needle.begin(), needle.end());
212+
benchmark::DoNotOptimize(result);
213+
}
214+
})
215+
->Arg(1000) // non power-of-two
216+
->Arg(1024)
217+
->Arg(8192);
218+
};
219+
register_benchmarks(bm, "match near end");
139220
}
140221

141222
benchmark::Initialize(&argc, argv);

0 commit comments

Comments
 (0)