Skip to content

Commit c44c2a2

Browse files
committed
Create new benchmark for set_intersection().
1 parent 7aa3927 commit c44c2a2

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

libcxx/benchmarks/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ set(BENCHMARK_TESTS
192192
algorithms/ranges_sort.bench.cpp
193193
algorithms/ranges_sort_heap.bench.cpp
194194
algorithms/ranges_stable_sort.bench.cpp
195+
algorithms/set_intersection.bench.cpp
195196
algorithms/sort.bench.cpp
196197
algorithms/sort_heap.bench.cpp
197198
algorithms/stable_sort.bench.cpp
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
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+
#include <algorithm>
10+
#include <forward_list>
11+
#include <iterator>
12+
#include <set>
13+
#include <vector>
14+
15+
#include "common.h"
16+
17+
namespace {
18+
19+
// types of containers we'll want to test, covering interesting iterator types
20+
struct VectorContainer {
21+
template <typename... Args>
22+
using type = std::vector<Args...>;
23+
24+
static constexpr const char* Name = "Vector";
25+
};
26+
27+
struct SetContainer {
28+
template <typename... Args>
29+
using type = std::set<Args...>;
30+
31+
static constexpr const char* Name = "Set";
32+
};
33+
34+
struct ForwardListContainer {
35+
template <typename... Args>
36+
using type = std::forward_list<Args...>;
37+
38+
static constexpr const char* Name = "ForwardList";
39+
};
40+
41+
using AllContainerTypes = std::tuple<VectorContainer, SetContainer, ForwardListContainer>;
42+
43+
// set_intersection performance may depend on where matching values lie
44+
enum class OverlapPosition {
45+
Nowhere,
46+
Front,
47+
Back,
48+
Interlaced,
49+
};
50+
51+
struct AllOverlapPositions : EnumValuesAsTuple<AllOverlapPositions, OverlapPosition, 4> {
52+
static constexpr const char* Names[] = {
53+
"Nowhere", "Front", "Back", "Interlaced"};
54+
};
55+
56+
// functor that moves elements from an iterator range into a new Container instance
57+
template <typename Container>
58+
struct MoveInto {};
59+
60+
template <typename T>
61+
struct MoveInto<std::vector<T>> {
62+
template <class It>
63+
[[nodiscard]] static std::vector<T> operator()(It first, It last) {
64+
std::vector<T> out;
65+
std::move(first, last, std::back_inserter(out));
66+
return out;
67+
}
68+
};
69+
70+
template <typename T>
71+
struct MoveInto<std::forward_list<T>> {
72+
template <class It>
73+
[[nodiscard]] static std::forward_list<T> operator()(It first, It last) {
74+
std::forward_list<T> out;
75+
std::move(first, last, std::front_inserter(out));
76+
out.reverse();
77+
return out;
78+
}
79+
};
80+
81+
template <typename T>
82+
struct MoveInto<std::set<T>> {
83+
template <class It>
84+
[[nodiscard]] static std::set<T> operator()(It first, It last) {
85+
std::set<T> out;
86+
std::move(first, last, std::inserter(out, out.begin()));
87+
return out;
88+
}
89+
};
90+
91+
// lightweight wrapping around fillValues() which puts a little effort into
92+
// making that would be contiguous when sorted non-contiguous in memory
93+
template <typename T>
94+
std::vector<T> getVectorOfRandom(size_t N) {
95+
std::vector<T> V;
96+
fillValues(V, N, Order::Random);
97+
sortValues(V, Order::Random);
98+
return std::vector<T>(V);
99+
}
100+
101+
// forward_iterator wrapping which, for each increment, moves the underlying iterator forward Stride elements
102+
template <typename Wrapped>
103+
struct StridedFwdIt {
104+
Wrapped Base;
105+
unsigned Stride;
106+
107+
using iterator_category = std::forward_iterator_tag;
108+
using difference_type = typename Wrapped::difference_type;
109+
using value_type = typename Wrapped::value_type;
110+
using pointer = typename Wrapped::pointer;
111+
using reference = typename Wrapped::reference;
112+
113+
StridedFwdIt(Wrapped B, unsigned Stride_) : Base(B), Stride(Stride_) { assert(Stride != 0); }
114+
115+
StridedFwdIt operator++() { for (unsigned I=0; I<Stride; ++I) ++Base; return *this; }
116+
StridedFwdIt operator++(int) { auto Tmp = *this; ++*this; return Tmp; }
117+
value_type& operator*() { return *Base; }
118+
const value_type& operator*() const { return *Base; }
119+
value_type& operator->() { return *Base; }
120+
const value_type& operator->() const { return *Base; }
121+
bool operator==(const StridedFwdIt& o) const { return Base==o.Base; }
122+
bool operator!=(const StridedFwdIt& o) const { return !operator==(o); }
123+
};
124+
template <typename Wrapped> StridedFwdIt(Wrapped, unsigned) -> StridedFwdIt<Wrapped>;
125+
126+
127+
// realistically, data won't all be nicely contiguous in a container
128+
// we'll go through some effort to ensure that it's shuffled through memory
129+
template <class Container>
130+
std::pair<Container, Container> genCacheUnfriendlyData(size_t Size1, size_t Size2, OverlapPosition Pos) {
131+
using ValueType = typename Container::value_type;
132+
const MoveInto<Container> moveInto;
133+
const auto SrcSize = Pos == OverlapPosition::Nowhere ? Size1 + Size2 : std::max(Size1, Size2);
134+
std::vector<ValueType> Src = getVectorOfRandom<ValueType>(SrcSize);
135+
136+
if (Pos == OverlapPosition::Nowhere) {
137+
std::sort(Src.begin(), Src.end());
138+
return std::make_pair(
139+
moveInto(Src.begin(), Src.begin() + Size1),
140+
moveInto(Src.begin() + Size1, Src.end()));
141+
}
142+
143+
// all other overlap types will have to copy some part of the data, but if
144+
// we copy after sorting it will likely have high cache locality, so we sort
145+
// each copy separately
146+
auto Copy = Src;
147+
std::sort(Src.begin(), Src.end());
148+
std::sort(Copy.begin(), Copy.end());
149+
150+
switch(Pos) {
151+
case OverlapPosition::Nowhere:
152+
break;
153+
154+
case OverlapPosition::Front:
155+
return std::make_pair(
156+
moveInto(Src.begin(), Src.begin() + Size1),
157+
moveInto(Copy.begin(), Copy.begin() + Size2));
158+
159+
case OverlapPosition::Back:
160+
return std::make_pair(
161+
moveInto(Src.begin() + (Src.size() - Size1), Src.end()),
162+
moveInto(Copy.begin() + (Copy.size() - Size2), Copy.end()));
163+
164+
case OverlapPosition::Interlaced:
165+
const auto Stride1 = Size1 < Size2 ? Size2/Size1 : 1;
166+
const auto Stride2 = Size2 < Size1 ? Size1/Size2 : 1;
167+
return std::make_pair(
168+
moveInto(StridedFwdIt(Src.begin(), Stride1), StridedFwdIt(Src.end(), Stride1)),
169+
moveInto(StridedFwdIt(Copy.begin(), Stride2), StridedFwdIt(Copy.end(), Stride2)));
170+
}
171+
abort();
172+
return std::pair<Container, Container>();
173+
}
174+
175+
176+
template <class ValueType, class Container, class Overlap>
177+
struct SetIntersection {
178+
using ContainerType = typename Container::template type<Value<ValueType>>;
179+
size_t Size1;
180+
size_t Size2;
181+
182+
SetIntersection(size_t M, size_t N) : Size1(M), Size2(N) {}
183+
184+
void run(benchmark::State& state) const {
185+
state.PauseTiming();
186+
auto Input = genCacheUnfriendlyData<ContainerType>(Size1, Size2, Overlap());
187+
std::vector<Value<ValueType>> out(std::min(Size1, Size2));
188+
189+
size_t cmp;
190+
auto trackingLess = [&cmp](const Value<ValueType>& lhs, const Value<ValueType>& rhs) {
191+
++cmp;
192+
return std::less<Value<ValueType>>{}(lhs, rhs);
193+
};
194+
195+
const auto BatchSize = std::max(size_t{16}, (2*TestSetElements) / (Size1+Size2));
196+
state.ResumeTiming();
197+
198+
for (const auto& _ : state) {
199+
while (state.KeepRunningBatch(BatchSize)) {
200+
for (unsigned i=0; i<BatchSize; ++i) {
201+
const auto& [C1, C2] = Input;
202+
auto outIter = std::set_intersection(C1.begin(), C1.end(), C2.begin(), C2.end(), out.begin(), trackingLess);
203+
benchmark::DoNotOptimize(outIter);
204+
state.counters["Comparisons"] = cmp;
205+
}
206+
}
207+
}
208+
}
209+
210+
std::string name() const {
211+
return std::string("SetIntersection") + Overlap::name() + '_' + Container::Name +
212+
ValueType::name() + '_' + std::to_string(Size1) + '_' + std::to_string(Size2);
213+
}
214+
};
215+
216+
} // namespace
217+
218+
int main(int argc, char** argv) {/**/
219+
benchmark::Initialize(&argc, argv);
220+
if (benchmark::ReportUnrecognizedArguments(argc, argv))
221+
return 1;
222+
makeCartesianProductBenchmark<SetIntersection, AllValueTypes, AllContainerTypes, AllOverlapPositions>(Quantities, Quantities);
223+
benchmark::RunSpecifiedBenchmarks();
224+
}

0 commit comments

Comments
 (0)