Skip to content

Commit 4107544

Browse files
authored
[libc++] Add benchmarks for partitioning algorithms (#127324)
This patch adds benchmarks for std::partition, is_partitioned, etc and their ranges:: variants.
1 parent 7e22b09 commit 4107544

File tree

7 files changed

+564
-131
lines changed

7 files changed

+564
-131
lines changed

libcxx/include/module.modulemap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@ module std [system] {
687687
}
688688
module ranges_partition {
689689
header "__algorithm/ranges_partition.h"
690+
export std.ranges.subrange // return type
690691
}
691692
module ranges_pop_heap {
692693
header "__algorithm/ranges_pop_heap.h"
@@ -786,6 +787,7 @@ module std [system] {
786787
}
787788
module ranges_stable_partition {
788789
header "__algorithm/ranges_stable_partition.h"
790+
export std.ranges.subrange // return type
789791
}
790792
module ranges_stable_sort {
791793
header "__algorithm/ranges_stable_sort.h"

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

Lines changed: 0 additions & 131 deletions
This file was deleted.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17
10+
11+
#include <algorithm>
12+
#include <cassert>
13+
#include <cstddef>
14+
#include <deque>
15+
#include <iterator>
16+
#include <list>
17+
#include <string>
18+
#include <vector>
19+
20+
#include "benchmark/benchmark.h"
21+
#include "../../GenerateInput.h"
22+
23+
auto compute_median(auto first, auto last) {
24+
std::vector v(first, last);
25+
auto middle = v.begin() + v.size() / 2;
26+
std::nth_element(v.begin(), middle, v.end());
27+
return *middle;
28+
}
29+
30+
int main(int argc, char** argv) {
31+
auto std_is_partitioned = [](auto first, auto last, auto pred) { return std::is_partitioned(first, last, pred); };
32+
33+
auto bm = []<class Container, bool Partitioned>(std::string name, auto is_partitioned) {
34+
benchmark::RegisterBenchmark(
35+
name,
36+
[is_partitioned](auto& st) {
37+
std::size_t const size = st.range(0);
38+
using ValueType = typename Container::value_type;
39+
Container c;
40+
std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
41+
42+
// Partition the container in two equally-sized halves, ensuring the median
43+
// value appears in the left half. Note that the median value isn't located
44+
// in the middle -- this isn't std::nth_element.
45+
ValueType median = compute_median(c.begin(), c.end());
46+
auto pred = [median](auto const& element) { return element <= median; };
47+
std::partition(c.begin(), c.end(), pred);
48+
assert(std::is_partitioned(c.begin(), c.end(), pred));
49+
50+
if constexpr (!Partitioned) {
51+
// De-partition the container by swapping the element containing the median
52+
// value with the last one.
53+
auto median_it = std::find(c.begin(), c.end(), median);
54+
auto last_it = std::next(c.begin(), c.size() - 1);
55+
std::iter_swap(median_it, last_it);
56+
assert(!std::is_partitioned(c.begin(), c.end(), pred));
57+
}
58+
59+
for ([[maybe_unused]] auto _ : st) {
60+
benchmark::DoNotOptimize(c);
61+
auto result = is_partitioned(c.begin(), c.end(), pred);
62+
benchmark::DoNotOptimize(result);
63+
}
64+
})
65+
->Arg(32)
66+
->Arg(50) // non power-of-two
67+
->Arg(1024)
68+
->Arg(8192);
69+
};
70+
71+
// std::is_partitioned
72+
bm.operator()<std::vector<int>, true>("std::is_partitioned(vector<int>) (partitioned)", std_is_partitioned);
73+
bm.operator()<std::vector<int>, false>("std::is_partitioned(vector<int>) (unpartitioned)", std_is_partitioned);
74+
75+
bm.operator()<std::deque<int>, true>("std::is_partitioned(deque<int>) (partitioned)", std_is_partitioned);
76+
bm.operator()<std::deque<int>, false>("std::is_partitioned(deque<int>) (unpartitioned)", std_is_partitioned);
77+
78+
bm.operator()<std::list<int>, true>("std::is_partitioned(list<int>) (partitioned)", std_is_partitioned);
79+
bm.operator()<std::list<int>, false>("std::is_partitioned(list<int>) (unpartitioned)", std_is_partitioned);
80+
81+
// ranges::is_partitioned
82+
bm.operator()<std::vector<int>, true>("rng::is_partitioned(vector<int>) (partitioned)", std::ranges::is_partitioned);
83+
bm.operator()<std::vector<int>, false>(
84+
"rng::is_partitioned(vector<int>) (unpartitioned)", std::ranges::is_partitioned);
85+
86+
bm.operator()<std::deque<int>, true>("rng::is_partitioned(deque<int>) (partitioned)", std::ranges::is_partitioned);
87+
bm.operator()<std::deque<int>, false>("rng::is_partitioned(deque<int>) (unpartitioned)", std::ranges::is_partitioned);
88+
89+
bm.operator()<std::list<int>, true>("rng::is_partitioned(list<int>) (partitioned)", std::ranges::is_partitioned);
90+
bm.operator()<std::list<int>, false>("rng::is_partitioned(list<int>) (unpartitioned)", std::ranges::is_partitioned);
91+
92+
benchmark::Initialize(&argc, argv);
93+
benchmark::RunSpecifiedBenchmarks();
94+
benchmark::Shutdown();
95+
return 0;
96+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17
10+
11+
#include <algorithm>
12+
#include <cassert>
13+
#include <cstddef>
14+
#include <deque>
15+
#include <iterator>
16+
#include <list>
17+
#include <string>
18+
#include <vector>
19+
20+
#include "benchmark/benchmark.h"
21+
#include "../../GenerateInput.h"
22+
23+
auto compute_median(auto first, auto last) {
24+
std::vector v(first, last);
25+
auto middle = v.begin() + v.size() / 2;
26+
std::nth_element(v.begin(), middle, v.end());
27+
return *middle;
28+
}
29+
30+
int main(int argc, char** argv) {
31+
auto std_partition = [](auto first, auto last, auto pred) { return std::partition(first, last, pred); };
32+
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);
108+
benchmark::DoNotOptimize(result);
109+
shuffle();
110+
}
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+
}
128+
129+
benchmark::Initialize(&argc, argv);
130+
benchmark::RunSpecifiedBenchmarks();
131+
benchmark::Shutdown();
132+
return 0;
133+
}

0 commit comments

Comments
 (0)