Skip to content

Commit 8c43588

Browse files
authored
[libc++] Refactor and add benchmark coverage for [alg.sort] (#128236)
This patch adds missing benchmark coverage for partial_sort, partial_sort_copy, is_sorted and is_sorted_until. It also refactors the existing benchmarks for sort and stable_sort to follow the consistent style of the new algorithm benchmarks. However, these benchmarks were notoriously slow to run since they tested multiple data patterns on multiple data types. To try to alleviate this, I reduced the benchmarks to only run on integral types and on a single non-integral type, which should faithfully show how the algorithm behaves for anything non-integral. However, this is technically a reduction in coverage.
1 parent b434bc4 commit 8c43588

12 files changed

+740
-200
lines changed

libcxx/test/benchmarks/algorithms/pstl.stable_sort.bench.cpp

Lines changed: 0 additions & 42 deletions
This file was deleted.

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

Lines changed: 0 additions & 40 deletions
This file was deleted.

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

Lines changed: 0 additions & 40 deletions
This file was deleted.

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

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
#ifndef LIBCXX_TEST_BENCHMARKS_ALGORITHMS_SORTING_COMMON_H
10+
#define LIBCXX_TEST_BENCHMARKS_ALGORITHMS_SORTING_COMMON_H
11+
12+
#include <algorithm>
13+
#include <cstddef>
14+
#include <numeric>
15+
#include <random>
16+
#include <type_traits>
17+
#include <vector>
18+
19+
namespace support {
20+
21+
// This function creates a vector with N int-like values.
22+
//
23+
// These values are arranged in such a way that they would invoke O(N^2)
24+
// behavior on any quick sort implementation that satisifies certain conditions.
25+
// Details are available in the following paper:
26+
//
27+
// "A Killer Adversary for Quicksort", M. D. McIlroy, Software-Practice &
28+
// Experience Volume 29 Issue 4 April 10, 1999 pp 341-344.
29+
// https://dl.acm.org/doi/10.5555/311868.311871.
30+
template <class T>
31+
std::vector<T> quicksort_adversarial_data(std::size_t n) {
32+
static_assert(std::is_integral_v<T>);
33+
assert(n > 0);
34+
35+
// If an element is equal to gas, it indicates that the value of the element
36+
// is still to be decided and may change over the course of time.
37+
T gas = n - 1;
38+
39+
std::vector<T> v;
40+
v.resize(n);
41+
for (unsigned int i = 0; i < n; ++i) {
42+
v[i] = gas;
43+
}
44+
// Candidate for the pivot position.
45+
int candidate = 0;
46+
int nsolid = 0;
47+
// Populate all positions in the generated input to gas.
48+
std::vector<int> ascending_values(v.size());
49+
50+
// Fill up with ascending values from 0 to v.size()-1. These will act as
51+
// indices into v.
52+
std::iota(ascending_values.begin(), ascending_values.end(), 0);
53+
std::sort(ascending_values.begin(), ascending_values.end(), [&](int x, int y) {
54+
if (v[x] == gas && v[y] == gas) {
55+
// We are comparing two inputs whose value is still to be decided.
56+
if (x == candidate) {
57+
v[x] = nsolid++;
58+
} else {
59+
v[y] = nsolid++;
60+
}
61+
}
62+
if (v[x] == gas) {
63+
candidate = x;
64+
} else if (v[y] == gas) {
65+
candidate = y;
66+
}
67+
return v[x] < v[y];
68+
});
69+
return v;
70+
}
71+
72+
// ascending sorted values
73+
template <class T>
74+
std::vector<T> ascending_sorted_data(std::size_t n) {
75+
std::vector<T> v(n);
76+
std::iota(v.begin(), v.end(), 0);
77+
return v;
78+
}
79+
80+
// descending sorted values
81+
template <class T>
82+
std::vector<T> descending_sorted_data(std::size_t n) {
83+
std::vector<T> v(n);
84+
std::iota(v.begin(), v.end(), 0);
85+
std::reverse(v.begin(), v.end());
86+
return v;
87+
}
88+
89+
// pipe-organ pattern
90+
template <class T>
91+
std::vector<T> pipe_organ_data(std::size_t n) {
92+
std::vector<T> v(n);
93+
std::iota(v.begin(), v.end(), 0);
94+
auto half = v.begin() + v.size() / 2;
95+
std::reverse(half, v.end());
96+
return v;
97+
}
98+
99+
// heap pattern
100+
template <class T>
101+
std::vector<T> heap_data(std::size_t n) {
102+
std::vector<T> v(n);
103+
std::iota(v.begin(), v.end(), 0);
104+
std::make_heap(v.begin(), v.end());
105+
return v;
106+
}
107+
108+
// shuffled randomly
109+
template <class T>
110+
std::vector<T> shuffled_data(std::size_t n) {
111+
std::vector<T> v(n);
112+
std::iota(v.begin(), v.end(), 0);
113+
std::mt19937 rng;
114+
std::shuffle(v.begin(), v.end(), rng);
115+
return v;
116+
}
117+
118+
// single element in the whole sequence
119+
template <class T>
120+
std::vector<T> single_element_data(std::size_t n) {
121+
std::vector<T> v(n);
122+
return v;
123+
}
124+
125+
struct NonIntegral {
126+
NonIntegral() : value_(0) {}
127+
NonIntegral(int i) : value_(i) {}
128+
friend auto operator<(NonIntegral const& a, NonIntegral const& b) { return a.value_ < b.value_; }
129+
friend auto operator>(NonIntegral const& a, NonIntegral const& b) { return a.value_ > b.value_; }
130+
friend auto operator<=(NonIntegral const& a, NonIntegral const& b) { return a.value_ <= b.value_; }
131+
friend auto operator>=(NonIntegral const& a, NonIntegral const& b) { return a.value_ >= b.value_; }
132+
friend auto operator==(NonIntegral const& a, NonIntegral const& b) { return a.value_ == b.value_; }
133+
friend auto operator!=(NonIntegral const& a, NonIntegral const& b) { return a.value_ != b.value_; }
134+
135+
private:
136+
int value_;
137+
};
138+
139+
} // namespace support
140+
141+
#endif // LIBCXX_TEST_BENCHMARKS_ALGORITHMS_SORTING_COMMON_H
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 <cstddef>
13+
#include <deque>
14+
#include <iterator>
15+
#include <list>
16+
#include <string>
17+
#include <vector>
18+
19+
#include "benchmark/benchmark.h"
20+
#include "../../GenerateInput.h"
21+
22+
int main(int argc, char** argv) {
23+
auto std_is_sorted = [](auto first, auto last) { return std::is_sorted(first, last); };
24+
auto std_is_sorted_pred = [](auto first, auto last) {
25+
return std::is_sorted(first, last, [](auto x, auto y) {
26+
benchmark::DoNotOptimize(x);
27+
benchmark::DoNotOptimize(y);
28+
return x < y;
29+
});
30+
};
31+
auto ranges_is_sorted_pred = [](auto first, auto last) {
32+
return std::ranges::is_sorted(first, last, [](auto x, auto y) {
33+
benchmark::DoNotOptimize(x);
34+
benchmark::DoNotOptimize(y);
35+
return x < y;
36+
});
37+
};
38+
39+
// Benchmark {std,ranges}::is_sorted on a sorted sequence (the worst case).
40+
{
41+
auto bm = []<class Container>(std::string name, auto is_sorted) {
42+
benchmark::RegisterBenchmark(
43+
name,
44+
[is_sorted](auto& st) {
45+
std::size_t const size = st.range(0);
46+
using ValueType = typename Container::value_type;
47+
std::vector<ValueType> data;
48+
std::generate_n(std::back_inserter(data), size, [] { return Generate<ValueType>::random(); });
49+
std::sort(data.begin(), data.end());
50+
51+
Container c(data.begin(), data.end());
52+
53+
for ([[maybe_unused]] auto _ : st) {
54+
benchmark::DoNotOptimize(c);
55+
auto result = is_sorted(c.begin(), c.end());
56+
benchmark::DoNotOptimize(result);
57+
}
58+
})
59+
->Arg(8)
60+
->Arg(1024)
61+
->Arg(8192);
62+
};
63+
bm.operator()<std::vector<int>>("std::is_sorted(vector<int>)", std_is_sorted);
64+
bm.operator()<std::deque<int>>("std::is_sorted(deque<int>)", std_is_sorted);
65+
bm.operator()<std::list<int>>("std::is_sorted(list<int>)", std_is_sorted);
66+
bm.operator()<std::vector<int>>("rng::is_sorted(vector<int>)", std::ranges::is_sorted);
67+
bm.operator()<std::deque<int>>("rng::is_sorted(deque<int>)", std::ranges::is_sorted);
68+
bm.operator()<std::list<int>>("rng::is_sorted(list<int>)", std::ranges::is_sorted);
69+
70+
bm.operator()<std::vector<int>>("std::is_sorted(vector<int>, pred)", std_is_sorted_pred);
71+
bm.operator()<std::deque<int>>("std::is_sorted(deque<int>, pred)", std_is_sorted_pred);
72+
bm.operator()<std::list<int>>("std::is_sorted(list<int>, pred)", std_is_sorted_pred);
73+
bm.operator()<std::vector<int>>("rng::is_sorted(vector<int>, pred)", ranges_is_sorted_pred);
74+
bm.operator()<std::deque<int>>("rng::is_sorted(deque<int>, pred)", ranges_is_sorted_pred);
75+
bm.operator()<std::list<int>>("rng::is_sorted(list<int>, pred)", ranges_is_sorted_pred);
76+
}
77+
78+
benchmark::Initialize(&argc, argv);
79+
benchmark::RunSpecifiedBenchmarks();
80+
benchmark::Shutdown();
81+
return 0;
82+
}

0 commit comments

Comments
 (0)