Skip to content

Commit 1770cf0

Browse files
committed
Address review comments
1 parent d3c1f5b commit 1770cf0

File tree

5 files changed

+243
-79
lines changed

5 files changed

+243
-79
lines changed

libcxx/test/benchmarks/algorithms/partitions/is_partitioned.bench.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ int main(int argc, char** argv) {
6363
}
6464
})
6565
->Arg(32)
66+
->Arg(50) // non power-of-two
6667
->Arg(1024)
6768
->Arg(8192);
6869
};

libcxx/test/benchmarks/algorithms/partitions/partition.bench.cpp

Lines changed: 94 additions & 39 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 <iterator>
@@ -29,47 +30,101 @@ auto compute_median(auto first, auto last) {
2930
int main(int argc, char** argv) {
3031
auto std_partition = [](auto first, auto last, auto pred) { return std::partition(first, last, pred); };
3132

32-
auto bm = []<class Container>(std::string name, auto partition) {
33-
benchmark::RegisterBenchmark(
34-
name,
35-
[partition](auto& st) {
36-
std::size_t const size = st.range(0);
37-
using ValueType = typename Container::value_type;
38-
Container c;
39-
std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
40-
41-
std::vector<ValueType> yes(size), no(size);
42-
ValueType median = compute_median(c.begin(), c.end());
43-
auto pred1 = [median](auto const& element) { return element < median; };
44-
auto pred2 = [median](auto const& element) { return element > median; };
45-
bool toggle = false;
46-
47-
for ([[maybe_unused]] auto _ : st) {
48-
benchmark::DoNotOptimize(c);
49-
if (toggle) {
50-
auto result = partition(c.begin(), c.end(), pred1);
51-
benchmark::DoNotOptimize(result);
52-
} else {
53-
auto result = partition(c.begin(), c.end(), pred2);
33+
// Benchmark {std,ranges}::partition on a fully unpartitionned sequence, i.e. a lot of elements
34+
// have to be moved around in order to partition the range.
35+
{
36+
auto bm = []<class Container>(std::string name, auto partition) {
37+
benchmark::RegisterBenchmark(
38+
name,
39+
[partition](auto& st) {
40+
std::size_t const size = st.range(0);
41+
using ValueType = typename Container::value_type;
42+
Container c;
43+
std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
44+
45+
ValueType median = compute_median(c.begin(), c.end());
46+
auto pred1 = [median](auto const& element) { return element < median; };
47+
auto pred2 = [median](auto const& element) { return element > median; };
48+
bool toggle = false;
49+
50+
for ([[maybe_unused]] auto _ : st) {
51+
benchmark::DoNotOptimize(c);
52+
// By toggling the predicate, we have to move almost all elements in the sequence
53+
// to restore the partition.
54+
if (toggle) {
55+
auto result = partition(c.begin(), c.end(), pred1);
56+
benchmark::DoNotOptimize(result);
57+
} else {
58+
auto result = partition(c.begin(), c.end(), pred2);
59+
benchmark::DoNotOptimize(result);
60+
}
61+
toggle = !toggle;
62+
}
63+
})
64+
->Arg(32)
65+
->Arg(50) // non power-of-two
66+
->Arg(1024)
67+
->Arg(8192);
68+
};
69+
70+
// std::partition
71+
bm.operator()<std::vector<int>>("std::partition(vector<int>) (dense)", std_partition);
72+
bm.operator()<std::deque<int>>("std::partition(deque<int>) (dense)", std_partition);
73+
bm.operator()<std::list<int>>("std::partition(list<int>) (dense)", std_partition);
74+
75+
// ranges::partition
76+
bm.operator()<std::vector<int>>("rng::partition(vector<int>) (dense)", std::ranges::partition);
77+
bm.operator()<std::deque<int>>("rng::partition(deque<int>) (dense)", std::ranges::partition);
78+
bm.operator()<std::list<int>>("rng::partition(list<int>) (dense)", std::ranges::partition);
79+
}
80+
81+
// Benchmark {std,ranges}::partition on a mostly partitioned sequence, i.e. only 10% of the elements
82+
// have to be moved around in order to partition the range.
83+
{
84+
auto bm = []<class Container>(std::string name, auto partition) {
85+
benchmark::RegisterBenchmark(
86+
name,
87+
[partition](auto& st) {
88+
std::size_t const size = st.range(0);
89+
using ValueType = typename Container::value_type;
90+
Container c;
91+
std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
92+
ValueType median = compute_median(c.begin(), c.end());
93+
auto pred = [median](auto const& element) { return element < median; };
94+
std::partition(c.begin(), c.end(), pred);
95+
96+
// Between iterations, we swap 5% of the elements to the left of the median with 5% of the elements
97+
// to the right of the median. This ensures that the range is slightly unpartitioned.
98+
auto median_it = std::partition_point(c.begin(), c.end(), pred);
99+
auto low = std::next(c.begin(), std::distance(c.begin(), median_it) - (size / 20));
100+
auto high = std::next(median_it, size / 20);
101+
auto shuffle = [&] { std::swap_ranges(low, median_it, high); };
102+
shuffle();
103+
assert(!std::is_partitioned(c.begin(), c.end(), pred));
104+
105+
for ([[maybe_unused]] auto _ : st) {
106+
benchmark::DoNotOptimize(c);
107+
auto result = partition(c.begin(), c.end(), pred);
54108
benchmark::DoNotOptimize(result);
109+
shuffle();
55110
}
56-
toggle = !toggle;
57-
}
58-
})
59-
->Arg(32)
60-
->Arg(1024)
61-
->Arg(8192);
62-
};
63-
64-
// std::partition
65-
bm.operator()<std::vector<int>>("std::partition(vector<int>)", std_partition);
66-
bm.operator()<std::deque<int>>("std::partition(deque<int>)", std_partition);
67-
bm.operator()<std::list<int>>("std::partition(list<int>)", std_partition);
68-
69-
// ranges::partition
70-
bm.operator()<std::vector<int>>("rng::partition(vector<int>)", std::ranges::partition);
71-
bm.operator()<std::deque<int>>("rng::partition(deque<int>)", std::ranges::partition);
72-
bm.operator()<std::list<int>>("rng::partition(list<int>)", std::ranges::partition);
111+
})
112+
->Arg(32)
113+
->Arg(50) // non power-of-two
114+
->Arg(1024)
115+
->Arg(8192);
116+
};
117+
118+
// std::partition
119+
bm.operator()<std::vector<int>>("std::partition(vector<int>) (sparse)", std_partition);
120+
bm.operator()<std::deque<int>>("std::partition(deque<int>) (sparse)", std_partition);
121+
bm.operator()<std::list<int>>("std::partition(list<int>) (sparse)", std_partition);
122+
123+
// ranges::partition
124+
bm.operator()<std::vector<int>>("rng::partition(vector<int>) (sparse)", std::ranges::partition);
125+
bm.operator()<std::deque<int>>("rng::partition(deque<int>) (sparse)", std::ranges::partition);
126+
bm.operator()<std::list<int>>("rng::partition(list<int>) (sparse)", std::ranges::partition);
127+
}
73128

74129
benchmark::Initialize(&argc, argv);
75130
benchmark::RunSpecifiedBenchmarks();

libcxx/test/benchmarks/algorithms/partitions/partition_copy.bench.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ int main(int argc, char** argv) {
4040
Container c;
4141
std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
4242

43-
std::vector<ValueType> yes(size), no(size);
43+
std::vector<ValueType> yes(size);
44+
std::vector<ValueType> no(size);
4445
ValueType median = compute_median(c.begin(), c.end());
4546
auto pred = [median](auto const& element) { return element < median; };
4647

@@ -53,6 +54,7 @@ int main(int argc, char** argv) {
5354
}
5455
})
5556
->Arg(32)
57+
->Arg(50) // non power-of-two
5658
->Arg(1024)
5759
->Arg(8192);
5860
};

libcxx/test/benchmarks/algorithms/partitions/partition_point.bench.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ int main(int argc, char** argv) {
5252
}
5353
})
5454
->Arg(32)
55+
->Arg(50) // non power-of-two
5556
->Arg(1024)
5657
->Arg(8192);
5758
};

libcxx/test/benchmarks/algorithms/partitions/stable_partition.bench.cpp

Lines changed: 144 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <string>
1717
#include <vector>
1818

19+
#include "count_new.h"
1920
#include "benchmark/benchmark.h"
2021
#include "../../GenerateInput.h"
2122

@@ -29,47 +30,151 @@ auto compute_median(auto first, auto last) {
2930
int main(int argc, char** argv) {
3031
auto std_stable_partition = [](auto first, auto last, auto pred) { return std::stable_partition(first, last, pred); };
3132

32-
auto bm = []<class Container>(std::string name, auto stable_partition) {
33-
benchmark::RegisterBenchmark(
34-
name,
35-
[stable_partition](auto& st) {
36-
std::size_t const size = st.range(0);
37-
using ValueType = typename Container::value_type;
38-
Container c;
39-
std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
40-
41-
std::vector<ValueType> yes(size), no(size);
42-
ValueType median = compute_median(c.begin(), c.end());
43-
auto pred1 = [median](auto const& element) { return element < median; };
44-
auto pred2 = [median](auto const& element) { return element > median; };
45-
bool toggle = false;
46-
47-
for ([[maybe_unused]] auto _ : st) {
48-
benchmark::DoNotOptimize(c);
49-
if (toggle) {
50-
auto result = stable_partition(c.begin(), c.end(), pred1);
51-
benchmark::DoNotOptimize(result);
52-
} else {
53-
auto result = stable_partition(c.begin(), c.end(), pred2);
33+
// Benchmark {std,ranges}::stable_partition on a fully unpartitionned sequence, i.e. a lot of elements
34+
// have to be moved around in order to partition the range.
35+
{
36+
auto bm = []<class Container>(std::string name, auto stable_partition) {
37+
benchmark::RegisterBenchmark(
38+
name,
39+
[stable_partition](auto& st) {
40+
std::size_t const size = st.range(0);
41+
using ValueType = typename Container::value_type;
42+
Container c;
43+
std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
44+
45+
ValueType median = compute_median(c.begin(), c.end());
46+
auto pred1 = [median](auto const& element) { return element < median; };
47+
auto pred2 = [median](auto const& element) { return element > median; };
48+
bool toggle = false;
49+
50+
for ([[maybe_unused]] auto _ : st) {
51+
benchmark::DoNotOptimize(c);
52+
// By toggling the predicate, we have to move almost all elements in the sequence
53+
// to restore the partition.
54+
if (toggle) {
55+
auto result = stable_partition(c.begin(), c.end(), pred1);
56+
benchmark::DoNotOptimize(result);
57+
} else {
58+
auto result = stable_partition(c.begin(), c.end(), pred2);
59+
benchmark::DoNotOptimize(result);
60+
}
61+
toggle = !toggle;
62+
}
63+
})
64+
->Arg(32)
65+
->Arg(50) // non power-of-two
66+
->Arg(1024)
67+
->Arg(8192);
68+
};
69+
70+
// std::stable_partition
71+
bm.operator()<std::vector<int>>("std::stable_partition(vector<int>) (dense)", std_stable_partition);
72+
bm.operator()<std::deque<int>>("std::stable_partition(deque<int>) (dense)", std_stable_partition);
73+
bm.operator()<std::list<int>>("std::stable_partition(list<int>) (dense)", std_stable_partition);
74+
75+
// ranges::stable_partition
76+
bm.operator()<std::vector<int>>("rng::stable_partition(vector<int>) (dense)", std::ranges::stable_partition);
77+
bm.operator()<std::deque<int>>("rng::stable_partition(deque<int>) (dense)", std::ranges::stable_partition);
78+
bm.operator()<std::list<int>>("rng::stable_partition(list<int>) (dense)", std::ranges::stable_partition);
79+
}
80+
81+
// Benchmark {std,ranges}::stable_partition on a mostly partitioned sequence, i.e. only 10% of the elements
82+
// have to be moved around in order to partition the range.
83+
{
84+
auto bm = []<class Container>(std::string name, auto stable_partition) {
85+
benchmark::RegisterBenchmark(
86+
name,
87+
[stable_partition](auto& st) {
88+
std::size_t const size = st.range(0);
89+
using ValueType = typename Container::value_type;
90+
Container c;
91+
std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
92+
ValueType median = compute_median(c.begin(), c.end());
93+
auto pred = [median](auto const& element) { return element < median; };
94+
std::partition(c.begin(), c.end(), pred);
95+
96+
// Between iterations, we swap 5% of the elements to the left of the median with 5% of the elements
97+
// to the right of the median. This ensures that the range is slightly unpartitioned.
98+
auto median_it = std::partition_point(c.begin(), c.end(), pred);
99+
auto low = std::next(c.begin(), std::distance(c.begin(), median_it) - (size / 20));
100+
auto high = std::next(median_it, size / 20);
101+
auto shuffle = [&] { std::swap_ranges(low, median_it, high); };
102+
shuffle();
103+
assert(!std::is_partitioned(c.begin(), c.end(), pred));
104+
105+
for ([[maybe_unused]] auto _ : st) {
106+
benchmark::DoNotOptimize(c);
107+
auto result = stable_partition(c.begin(), c.end(), pred);
54108
benchmark::DoNotOptimize(result);
109+
shuffle();
55110
}
56-
toggle = !toggle;
57-
}
58-
})
59-
->Arg(32)
60-
->Arg(1024)
61-
->Arg(8192);
62-
};
63-
64-
// std::stable_partition
65-
bm.operator()<std::vector<int>>("std::stable_partition(vector<int>)", std_stable_partition);
66-
bm.operator()<std::deque<int>>("std::stable_partition(deque<int>)", std_stable_partition);
67-
bm.operator()<std::list<int>>("std::stable_partition(list<int>)", std_stable_partition);
68-
69-
// ranges::stable_partition
70-
bm.operator()<std::vector<int>>("rng::stable_partition(vector<int>)", std::ranges::stable_partition);
71-
bm.operator()<std::deque<int>>("rng::stable_partition(deque<int>)", std::ranges::stable_partition);
72-
bm.operator()<std::list<int>>("rng::stable_partition(list<int>)", std::ranges::stable_partition);
111+
})
112+
->Arg(32)
113+
->Arg(50) // non power-of-two
114+
->Arg(1024)
115+
->Arg(8192);
116+
};
117+
118+
// std::stable_partition
119+
bm.operator()<std::vector<int>>("std::stable_partition(vector<int>) (sparse)", std_stable_partition);
120+
bm.operator()<std::deque<int>>("std::stable_partition(deque<int>) (sparse)", std_stable_partition);
121+
bm.operator()<std::list<int>>("std::stable_partition(list<int>) (sparse)", std_stable_partition);
122+
123+
// ranges::stable_partition
124+
bm.operator()<std::vector<int>>("rng::stable_partition(vector<int>) (sparse)", std::ranges::stable_partition);
125+
bm.operator()<std::deque<int>>("rng::stable_partition(deque<int>) (sparse)", std::ranges::stable_partition);
126+
bm.operator()<std::list<int>>("rng::stable_partition(list<int>) (sparse)", std::ranges::stable_partition);
127+
}
128+
129+
// Benchmark {std,ranges}::stable_partition when memory allocation fails. The algorithm must fall back to
130+
// a different algorithm that has different complexity guarantees.
131+
{
132+
auto bm = []<class Container>(std::string name, auto stable_partition) {
133+
benchmark::RegisterBenchmark(
134+
name,
135+
[stable_partition](auto& st) {
136+
std::size_t const size = st.range(0);
137+
using ValueType = typename Container::value_type;
138+
Container c;
139+
std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
140+
141+
ValueType median = compute_median(c.begin(), c.end());
142+
auto pred1 = [median](auto const& element) { return element < median; };
143+
auto pred2 = [median](auto const& element) { return element > median; };
144+
bool toggle = false;
145+
146+
for ([[maybe_unused]] auto _ : st) {
147+
benchmark::DoNotOptimize(c);
148+
// Disable the ability to allocate memory inside this block
149+
globalMemCounter.reset();
150+
globalMemCounter.throw_after = 0;
151+
152+
if (toggle) {
153+
auto result = stable_partition(c.begin(), c.end(), pred1);
154+
benchmark::DoNotOptimize(result);
155+
} else {
156+
auto result = stable_partition(c.begin(), c.end(), pred2);
157+
benchmark::DoNotOptimize(result);
158+
}
159+
toggle = !toggle;
160+
}
161+
})
162+
->Arg(32)
163+
->Arg(50) // non power-of-two
164+
->Arg(1024)
165+
->Arg(8192);
166+
};
167+
168+
// std::stable_partition
169+
bm.operator()<std::vector<int>>("std::stable_partition(vector<int>) (no alloc)", std_stable_partition);
170+
bm.operator()<std::deque<int>>("std::stable_partition(deque<int>) (no alloc)", std_stable_partition);
171+
bm.operator()<std::list<int>>("std::stable_partition(list<int>) (no alloc)", std_stable_partition);
172+
173+
// ranges::stable_partition
174+
bm.operator()<std::vector<int>>("rng::stable_partition(vector<int>) (no alloc)", std::ranges::stable_partition);
175+
bm.operator()<std::deque<int>>("rng::stable_partition(deque<int>) (no alloc)", std::ranges::stable_partition);
176+
bm.operator()<std::list<int>>("rng::stable_partition(list<int>) (no alloc)", std::ranges::stable_partition);
177+
}
73178

74179
benchmark::Initialize(&argc, argv);
75180
benchmark::RunSpecifiedBenchmarks();

0 commit comments

Comments
 (0)