1*4bdff4beSrobert //===----------------------------------------------------------------------===//
2*4bdff4beSrobert //
3*4bdff4beSrobert // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*4bdff4beSrobert // See https://llvm.org/LICENSE.txt for license information.
5*4bdff4beSrobert // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*4bdff4beSrobert //
7*4bdff4beSrobert //===----------------------------------------------------------------------===//
8*4bdff4beSrobert
9*4bdff4beSrobert #ifndef LIBCXX_ALGORITHMS_COMMON_H
10*4bdff4beSrobert #define LIBCXX_ALGORITHMS_COMMON_H
11*4bdff4beSrobert
12*4bdff4beSrobert #include <algorithm>
13*4bdff4beSrobert #include <numeric>
14*4bdff4beSrobert #include <tuple>
15*4bdff4beSrobert #include <vector>
16*4bdff4beSrobert
17*4bdff4beSrobert #include "../CartesianBenchmarks.h"
18*4bdff4beSrobert #include "../GenerateInput.h"
19*4bdff4beSrobert
20*4bdff4beSrobert enum class ValueType { Uint32, Uint64, Pair, Tuple, String, Float };
21*4bdff4beSrobert struct AllValueTypes : EnumValuesAsTuple<AllValueTypes, ValueType, 6> {
22*4bdff4beSrobert static constexpr const char* Names[] = {"uint32", "uint64", "pair<uint32, uint32>", "tuple<uint32, uint64, uint32>",
23*4bdff4beSrobert "string", "float"};
24*4bdff4beSrobert };
25*4bdff4beSrobert
26*4bdff4beSrobert using Types = std::tuple< uint32_t, uint64_t, std::pair<uint32_t, uint32_t>, std::tuple<uint32_t, uint64_t, uint32_t>,
27*4bdff4beSrobert std::string, float >;
28*4bdff4beSrobert
29*4bdff4beSrobert template <class V>
30*4bdff4beSrobert using Value = std::tuple_element_t<(int)V::value, Types>;
31*4bdff4beSrobert
32*4bdff4beSrobert enum class Order {
33*4bdff4beSrobert Random,
34*4bdff4beSrobert Ascending,
35*4bdff4beSrobert Descending,
36*4bdff4beSrobert SingleElement,
37*4bdff4beSrobert PipeOrgan,
38*4bdff4beSrobert Heap,
39*4bdff4beSrobert QuickSortAdversary,
40*4bdff4beSrobert };
41*4bdff4beSrobert struct AllOrders : EnumValuesAsTuple<AllOrders, Order, 7> {
42*4bdff4beSrobert static constexpr const char* Names[] = {"Random", "Ascending",
43*4bdff4beSrobert "Descending", "SingleElement",
44*4bdff4beSrobert "PipeOrgan", "Heap",
45*4bdff4beSrobert "QuickSortAdversary"};
46*4bdff4beSrobert };
47*4bdff4beSrobert
48*4bdff4beSrobert // fillAdversarialQuickSortInput fills the input vector with N int-like values.
49*4bdff4beSrobert // These values are arranged in such a way that they would invoke O(N^2)
50*4bdff4beSrobert // behavior on any quick sort implementation that satisifies certain conditions.
51*4bdff4beSrobert // Details are available in the following paper:
52*4bdff4beSrobert // "A Killer Adversary for Quicksort", M. D. McIlroy, Software-Practice &
53*4bdff4beSrobert // Experience Volume 29 Issue 4 April 10, 1999 pp 341-344.
54*4bdff4beSrobert // https://dl.acm.org/doi/10.5555/311868.311871.
55*4bdff4beSrobert template <class T>
fillAdversarialQuickSortInput(T & V,size_t N)56*4bdff4beSrobert void fillAdversarialQuickSortInput(T& V, size_t N) {
57*4bdff4beSrobert assert(N > 0);
58*4bdff4beSrobert // If an element is equal to gas, it indicates that the value of the element
59*4bdff4beSrobert // is still to be decided and may change over the course of time.
60*4bdff4beSrobert const unsigned int gas = N - 1;
61*4bdff4beSrobert V.resize(N);
62*4bdff4beSrobert for (unsigned int i = 0; i < N; ++i) {
63*4bdff4beSrobert V[i] = gas;
64*4bdff4beSrobert }
65*4bdff4beSrobert // Candidate for the pivot position.
66*4bdff4beSrobert int candidate = 0;
67*4bdff4beSrobert int nsolid = 0;
68*4bdff4beSrobert // Populate all positions in the generated input to gas.
69*4bdff4beSrobert std::vector<int> ascVals(V.size());
70*4bdff4beSrobert // Fill up with ascending values from 0 to V.size()-1. These will act as
71*4bdff4beSrobert // indices into V.
72*4bdff4beSrobert std::iota(ascVals.begin(), ascVals.end(), 0);
73*4bdff4beSrobert std::sort(ascVals.begin(), ascVals.end(), [&](int x, int y) {
74*4bdff4beSrobert if (V[x] == gas && V[y] == gas) {
75*4bdff4beSrobert // We are comparing two inputs whose value is still to be decided.
76*4bdff4beSrobert if (x == candidate) {
77*4bdff4beSrobert V[x] = nsolid++;
78*4bdff4beSrobert } else {
79*4bdff4beSrobert V[y] = nsolid++;
80*4bdff4beSrobert }
81*4bdff4beSrobert }
82*4bdff4beSrobert if (V[x] == gas) {
83*4bdff4beSrobert candidate = x;
84*4bdff4beSrobert } else if (V[y] == gas) {
85*4bdff4beSrobert candidate = y;
86*4bdff4beSrobert }
87*4bdff4beSrobert return V[x] < V[y];
88*4bdff4beSrobert });
89*4bdff4beSrobert }
90*4bdff4beSrobert
91*4bdff4beSrobert template <typename T>
fillValues(std::vector<T> & V,size_t N,Order O)92*4bdff4beSrobert void fillValues(std::vector<T>& V, size_t N, Order O) {
93*4bdff4beSrobert if (O == Order::SingleElement) {
94*4bdff4beSrobert V.resize(N, 0);
95*4bdff4beSrobert } else if (O == Order::QuickSortAdversary) {
96*4bdff4beSrobert fillAdversarialQuickSortInput(V, N);
97*4bdff4beSrobert } else {
98*4bdff4beSrobert while (V.size() < N)
99*4bdff4beSrobert V.push_back(V.size());
100*4bdff4beSrobert }
101*4bdff4beSrobert }
102*4bdff4beSrobert
103*4bdff4beSrobert template <typename T>
fillValues(std::vector<std::pair<T,T>> & V,size_t N,Order O)104*4bdff4beSrobert void fillValues(std::vector<std::pair<T, T> >& V, size_t N, Order O) {
105*4bdff4beSrobert if (O == Order::SingleElement) {
106*4bdff4beSrobert V.resize(N, std::make_pair(0, 0));
107*4bdff4beSrobert } else {
108*4bdff4beSrobert while (V.size() < N)
109*4bdff4beSrobert // Half of array will have the same first element.
110*4bdff4beSrobert if (V.size() % 2) {
111*4bdff4beSrobert V.push_back(std::make_pair(V.size(), V.size()));
112*4bdff4beSrobert } else {
113*4bdff4beSrobert V.push_back(std::make_pair(0, V.size()));
114*4bdff4beSrobert }
115*4bdff4beSrobert }
116*4bdff4beSrobert }
117*4bdff4beSrobert
118*4bdff4beSrobert template <typename T1, typename T2, typename T3>
fillValues(std::vector<std::tuple<T1,T2,T3>> & V,size_t N,Order O)119*4bdff4beSrobert void fillValues(std::vector<std::tuple<T1, T2, T3> >& V, size_t N, Order O) {
120*4bdff4beSrobert if (O == Order::SingleElement) {
121*4bdff4beSrobert V.resize(N, std::make_tuple(0, 0, 0));
122*4bdff4beSrobert } else {
123*4bdff4beSrobert while (V.size() < N)
124*4bdff4beSrobert // One third of array will have the same first element.
125*4bdff4beSrobert // One third of array will have the same first element and the same second element.
126*4bdff4beSrobert switch (V.size() % 3) {
127*4bdff4beSrobert case 0:
128*4bdff4beSrobert V.push_back(std::make_tuple(V.size(), V.size(), V.size()));
129*4bdff4beSrobert break;
130*4bdff4beSrobert case 1:
131*4bdff4beSrobert V.push_back(std::make_tuple(0, V.size(), V.size()));
132*4bdff4beSrobert break;
133*4bdff4beSrobert case 2:
134*4bdff4beSrobert V.push_back(std::make_tuple(0, 0, V.size()));
135*4bdff4beSrobert break;
136*4bdff4beSrobert }
137*4bdff4beSrobert }
138*4bdff4beSrobert }
139*4bdff4beSrobert
fillValues(std::vector<std::string> & V,size_t N,Order O)140*4bdff4beSrobert inline void fillValues(std::vector<std::string>& V, size_t N, Order O) {
141*4bdff4beSrobert if (O == Order::SingleElement) {
142*4bdff4beSrobert V.resize(N, getRandomString(64));
143*4bdff4beSrobert } else {
144*4bdff4beSrobert while (V.size() < N)
145*4bdff4beSrobert V.push_back(getRandomString(64));
146*4bdff4beSrobert }
147*4bdff4beSrobert }
148*4bdff4beSrobert
149*4bdff4beSrobert template <class T>
sortValues(T & V,Order O)150*4bdff4beSrobert void sortValues(T& V, Order O) {
151*4bdff4beSrobert switch (O) {
152*4bdff4beSrobert case Order::Random: {
153*4bdff4beSrobert std::random_device R;
154*4bdff4beSrobert std::mt19937 M(R());
155*4bdff4beSrobert std::shuffle(V.begin(), V.end(), M);
156*4bdff4beSrobert break;
157*4bdff4beSrobert }
158*4bdff4beSrobert case Order::Ascending:
159*4bdff4beSrobert std::sort(V.begin(), V.end());
160*4bdff4beSrobert break;
161*4bdff4beSrobert case Order::Descending:
162*4bdff4beSrobert std::sort(V.begin(), V.end(), std::greater<>());
163*4bdff4beSrobert break;
164*4bdff4beSrobert case Order::SingleElement:
165*4bdff4beSrobert // Nothing to do
166*4bdff4beSrobert break;
167*4bdff4beSrobert case Order::PipeOrgan:
168*4bdff4beSrobert std::sort(V.begin(), V.end());
169*4bdff4beSrobert std::reverse(V.begin() + V.size() / 2, V.end());
170*4bdff4beSrobert break;
171*4bdff4beSrobert case Order::Heap:
172*4bdff4beSrobert std::make_heap(V.begin(), V.end());
173*4bdff4beSrobert break;
174*4bdff4beSrobert case Order::QuickSortAdversary:
175*4bdff4beSrobert // Nothing to do
176*4bdff4beSrobert break;
177*4bdff4beSrobert }
178*4bdff4beSrobert }
179*4bdff4beSrobert
180*4bdff4beSrobert constexpr size_t TestSetElements =
181*4bdff4beSrobert #if !TEST_HAS_FEATURE(memory_sanitizer)
182*4bdff4beSrobert 1 << 18;
183*4bdff4beSrobert #else
184*4bdff4beSrobert 1 << 14;
185*4bdff4beSrobert #endif
186*4bdff4beSrobert
187*4bdff4beSrobert template <class ValueType>
makeOrderedValues(size_t N,Order O)188*4bdff4beSrobert std::vector<std::vector<Value<ValueType> > > makeOrderedValues(size_t N,
189*4bdff4beSrobert Order O) {
190*4bdff4beSrobert std::vector<std::vector<Value<ValueType> > > Ret;
191*4bdff4beSrobert const size_t NumCopies = std::max(size_t{1}, TestSetElements / N);
192*4bdff4beSrobert Ret.resize(NumCopies);
193*4bdff4beSrobert for (auto& V : Ret) {
194*4bdff4beSrobert fillValues(V, N, O);
195*4bdff4beSrobert sortValues(V, O);
196*4bdff4beSrobert }
197*4bdff4beSrobert return Ret;
198*4bdff4beSrobert }
199*4bdff4beSrobert
200*4bdff4beSrobert template <class T, class U>
resetCopies(benchmark::State & state,T & Copies,U & Orig)201*4bdff4beSrobert TEST_ALWAYS_INLINE void resetCopies(benchmark::State& state, T& Copies,
202*4bdff4beSrobert U& Orig) {
203*4bdff4beSrobert state.PauseTiming();
204*4bdff4beSrobert for (auto& Copy : Copies)
205*4bdff4beSrobert Copy = Orig;
206*4bdff4beSrobert state.ResumeTiming();
207*4bdff4beSrobert }
208*4bdff4beSrobert
209*4bdff4beSrobert enum class BatchSize {
210*4bdff4beSrobert CountElements,
211*4bdff4beSrobert CountBatch,
212*4bdff4beSrobert };
213*4bdff4beSrobert
214*4bdff4beSrobert template <class ValueType, class F>
runOpOnCopies(benchmark::State & state,size_t Quantity,Order O,BatchSize Count,F Body)215*4bdff4beSrobert void runOpOnCopies(benchmark::State& state, size_t Quantity, Order O,
216*4bdff4beSrobert BatchSize Count, F Body) {
217*4bdff4beSrobert auto Copies = makeOrderedValues<ValueType>(Quantity, O);
218*4bdff4beSrobert auto Orig = Copies;
219*4bdff4beSrobert
220*4bdff4beSrobert const size_t Batch = Count == BatchSize::CountElements
221*4bdff4beSrobert ? Copies.size() * Quantity
222*4bdff4beSrobert : Copies.size();
223*4bdff4beSrobert while (state.KeepRunningBatch(Batch)) {
224*4bdff4beSrobert for (auto& Copy : Copies) {
225*4bdff4beSrobert Body(Copy);
226*4bdff4beSrobert benchmark::DoNotOptimize(Copy);
227*4bdff4beSrobert }
228*4bdff4beSrobert state.PauseTiming();
229*4bdff4beSrobert Copies = Orig;
230*4bdff4beSrobert state.ResumeTiming();
231*4bdff4beSrobert }
232*4bdff4beSrobert }
233*4bdff4beSrobert
234*4bdff4beSrobert
235*4bdff4beSrobert const std::vector<size_t> Quantities = {1 << 0, 1 << 2, 1 << 4, 1 << 6,
236*4bdff4beSrobert 1 << 8, 1 << 10, 1 << 14,
237*4bdff4beSrobert // Running each benchmark in parallel consumes too much memory with MSAN
238*4bdff4beSrobert // and can lead to the test process being killed.
239*4bdff4beSrobert #if !TEST_HAS_FEATURE(memory_sanitizer)
240*4bdff4beSrobert 1 << 18
241*4bdff4beSrobert #endif
242*4bdff4beSrobert };
243*4bdff4beSrobert
244*4bdff4beSrobert #endif // LIBCXX_ALGORITHMS_COMMON_H
245