1 // Copyright 2018 The Abseil Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "absl/container/internal/raw_hash_set.h"
16
17 #include <numeric>
18 #include <random>
19
20 #include "absl/base/internal/raw_logging.h"
21 #include "absl/container/internal/hash_function_defaults.h"
22 #include "absl/strings/str_format.h"
23 #include "benchmark/benchmark.h"
24
25 namespace absl {
26 ABSL_NAMESPACE_BEGIN
27 namespace container_internal {
28
29 struct RawHashSetTestOnlyAccess {
30 template <typename C>
GetSlotsabsl::container_internal::RawHashSetTestOnlyAccess31 static auto GetSlots(const C& c) -> decltype(c.slots_) {
32 return c.slots_;
33 }
34 };
35
36 namespace {
37
38 struct IntPolicy {
39 using slot_type = int64_t;
40 using key_type = int64_t;
41 using init_type = int64_t;
42
constructabsl::container_internal::__anonf47cbabb0111::IntPolicy43 static void construct(void*, int64_t* slot, int64_t v) { *slot = v; }
destroyabsl::container_internal::__anonf47cbabb0111::IntPolicy44 static void destroy(void*, int64_t*) {}
transferabsl::container_internal::__anonf47cbabb0111::IntPolicy45 static void transfer(void*, int64_t* new_slot, int64_t* old_slot) {
46 *new_slot = *old_slot;
47 }
48
elementabsl::container_internal::__anonf47cbabb0111::IntPolicy49 static int64_t& element(slot_type* slot) { return *slot; }
50
51 template <class F>
applyabsl::container_internal::__anonf47cbabb0111::IntPolicy52 static auto apply(F&& f, int64_t x) -> decltype(std::forward<F>(f)(x, x)) {
53 return std::forward<F>(f)(x, x);
54 }
55 };
56
57 class StringPolicy {
58 template <class F, class K, class V,
59 class = typename std::enable_if<
60 std::is_convertible<const K&, absl::string_view>::value>::type>
61 decltype(std::declval<F>()(
62 std::declval<const absl::string_view&>(), std::piecewise_construct,
63 std::declval<std::tuple<K>>(),
apply_impl(F && f,std::pair<std::tuple<K>,V> p)64 std::declval<V>())) static apply_impl(F&& f,
65 std::pair<std::tuple<K>, V> p) {
66 const absl::string_view& key = std::get<0>(p.first);
67 return std::forward<F>(f)(key, std::piecewise_construct, std::move(p.first),
68 std::move(p.second));
69 }
70
71 public:
72 struct slot_type {
73 struct ctor {};
74
75 template <class... Ts>
slot_typeabsl::container_internal::__anonf47cbabb0111::StringPolicy::slot_type76 slot_type(ctor, Ts&&... ts) : pair(std::forward<Ts>(ts)...) {}
77
78 std::pair<std::string, std::string> pair;
79 };
80
81 using key_type = std::string;
82 using init_type = std::pair<std::string, std::string>;
83
84 template <class allocator_type, class... Args>
construct(allocator_type * alloc,slot_type * slot,Args...args)85 static void construct(allocator_type* alloc, slot_type* slot, Args... args) {
86 std::allocator_traits<allocator_type>::construct(
87 *alloc, slot, typename slot_type::ctor(), std::forward<Args>(args)...);
88 }
89
90 template <class allocator_type>
destroy(allocator_type * alloc,slot_type * slot)91 static void destroy(allocator_type* alloc, slot_type* slot) {
92 std::allocator_traits<allocator_type>::destroy(*alloc, slot);
93 }
94
95 template <class allocator_type>
transfer(allocator_type * alloc,slot_type * new_slot,slot_type * old_slot)96 static void transfer(allocator_type* alloc, slot_type* new_slot,
97 slot_type* old_slot) {
98 construct(alloc, new_slot, std::move(old_slot->pair));
99 destroy(alloc, old_slot);
100 }
101
element(slot_type * slot)102 static std::pair<std::string, std::string>& element(slot_type* slot) {
103 return slot->pair;
104 }
105
106 template <class F, class... Args>
apply(F && f,Args &&...args)107 static auto apply(F&& f, Args&&... args)
108 -> decltype(apply_impl(std::forward<F>(f),
109 PairArgs(std::forward<Args>(args)...))) {
110 return apply_impl(std::forward<F>(f),
111 PairArgs(std::forward<Args>(args)...));
112 }
113 };
114
115 struct StringHash : container_internal::hash_default_hash<absl::string_view> {
116 using is_transparent = void;
117 };
118 struct StringEq : std::equal_to<absl::string_view> {
119 using is_transparent = void;
120 };
121
122 struct StringTable
123 : raw_hash_set<StringPolicy, StringHash, StringEq, std::allocator<int>> {
124 using Base = typename StringTable::raw_hash_set;
StringTableabsl::container_internal::__anonf47cbabb0111::StringTable125 StringTable() {}
126 using Base::Base;
127 };
128
129 struct IntTable
130 : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>,
131 std::equal_to<int64_t>, std::allocator<int64_t>> {
132 using Base = typename IntTable::raw_hash_set;
IntTableabsl::container_internal::__anonf47cbabb0111::IntTable133 IntTable() {}
134 using Base::Base;
135 };
136
137 struct string_generator {
138 template <class RNG>
operator ()absl::container_internal::__anonf47cbabb0111::string_generator139 std::string operator()(RNG& rng) const {
140 std::string res;
141 res.resize(12);
142 std::uniform_int_distribution<uint32_t> printable_ascii(0x20, 0x7E);
143 std::generate(res.begin(), res.end(), [&] { return printable_ascii(rng); });
144 return res;
145 }
146
147 size_t size;
148 };
149
150 // Model a cache in steady state.
151 //
152 // On a table of size N, keep deleting the LRU entry and add a random one.
BM_CacheInSteadyState(benchmark::State & state)153 void BM_CacheInSteadyState(benchmark::State& state) {
154 std::random_device rd;
155 std::mt19937 rng(rd());
156 string_generator gen{12};
157 StringTable t;
158 std::deque<std::string> keys;
159 while (t.size() < state.range(0)) {
160 auto x = t.emplace(gen(rng), gen(rng));
161 if (x.second) keys.push_back(x.first->first);
162 }
163 ABSL_RAW_CHECK(state.range(0) >= 10, "");
164 while (state.KeepRunning()) {
165 // Some cache hits.
166 std::deque<std::string>::const_iterator it;
167 for (int i = 0; i != 90; ++i) {
168 if (i % 10 == 0) it = keys.end();
169 ::benchmark::DoNotOptimize(t.find(*--it));
170 }
171 // Some cache misses.
172 for (int i = 0; i != 10; ++i) ::benchmark::DoNotOptimize(t.find(gen(rng)));
173 ABSL_RAW_CHECK(t.erase(keys.front()), keys.front().c_str());
174 keys.pop_front();
175 while (true) {
176 auto x = t.emplace(gen(rng), gen(rng));
177 if (x.second) {
178 keys.push_back(x.first->first);
179 break;
180 }
181 }
182 }
183 state.SetItemsProcessed(state.iterations());
184 state.SetLabel(absl::StrFormat("load_factor=%.2f", t.load_factor()));
185 }
186
187 template <typename Benchmark>
CacheInSteadyStateArgs(Benchmark * bm)188 void CacheInSteadyStateArgs(Benchmark* bm) {
189 // The default.
190 const float max_load_factor = 0.875;
191 // When the cache is at the steady state, the probe sequence will equal
192 // capacity if there is no reclamation of deleted slots. Pick a number large
193 // enough to make the benchmark slow for that case.
194 const size_t capacity = 1 << 10;
195
196 // Check N data points to cover load factors in [0.4, 0.8).
197 const size_t kNumPoints = 10;
198 for (size_t i = 0; i != kNumPoints; ++i)
199 bm->Arg(std::ceil(
200 capacity * (max_load_factor + i * max_load_factor / kNumPoints) / 2));
201 }
202 BENCHMARK(BM_CacheInSteadyState)->Apply(CacheInSteadyStateArgs);
203
BM_EndComparison(benchmark::State & state)204 void BM_EndComparison(benchmark::State& state) {
205 std::random_device rd;
206 std::mt19937 rng(rd());
207 string_generator gen{12};
208 StringTable t;
209 while (t.size() < state.range(0)) {
210 t.emplace(gen(rng), gen(rng));
211 }
212
213 for (auto _ : state) {
214 for (auto it = t.begin(); it != t.end(); ++it) {
215 benchmark::DoNotOptimize(it);
216 benchmark::DoNotOptimize(t);
217 benchmark::DoNotOptimize(it != t.end());
218 }
219 }
220 }
221 BENCHMARK(BM_EndComparison)->Arg(400);
222
BM_CopyCtor(benchmark::State & state)223 void BM_CopyCtor(benchmark::State& state) {
224 std::random_device rd;
225 std::mt19937 rng(rd());
226 IntTable t;
227 std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
228
229 while (t.size() < state.range(0)) {
230 t.emplace(dist(rng));
231 }
232
233 for (auto _ : state) {
234 IntTable t2 = t;
235 benchmark::DoNotOptimize(t2);
236 }
237 }
238 BENCHMARK(BM_CopyCtor)->Range(128, 4096);
239
BM_CopyAssign(benchmark::State & state)240 void BM_CopyAssign(benchmark::State& state) {
241 std::random_device rd;
242 std::mt19937 rng(rd());
243 IntTable t;
244 std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
245 while (t.size() < state.range(0)) {
246 t.emplace(dist(rng));
247 }
248
249 IntTable t2;
250 for (auto _ : state) {
251 t2 = t;
252 benchmark::DoNotOptimize(t2);
253 }
254 }
255 BENCHMARK(BM_CopyAssign)->Range(128, 4096);
256
BM_RangeCtor(benchmark::State & state)257 void BM_RangeCtor(benchmark::State& state) {
258 std::random_device rd;
259 std::mt19937 rng(rd());
260 std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
261 std::vector<int> values;
262 const size_t desired_size = state.range(0);
263 while (values.size() < desired_size) {
264 values.emplace_back(dist(rng));
265 }
266
267 for (auto unused : state) {
268 IntTable t{values.begin(), values.end()};
269 benchmark::DoNotOptimize(t);
270 }
271 }
272 BENCHMARK(BM_RangeCtor)->Range(128, 65536);
273
BM_NoOpReserveIntTable(benchmark::State & state)274 void BM_NoOpReserveIntTable(benchmark::State& state) {
275 IntTable t;
276 t.reserve(100000);
277 for (auto _ : state) {
278 benchmark::DoNotOptimize(t);
279 t.reserve(100000);
280 }
281 }
282 BENCHMARK(BM_NoOpReserveIntTable);
283
BM_NoOpReserveStringTable(benchmark::State & state)284 void BM_NoOpReserveStringTable(benchmark::State& state) {
285 StringTable t;
286 t.reserve(100000);
287 for (auto _ : state) {
288 benchmark::DoNotOptimize(t);
289 t.reserve(100000);
290 }
291 }
292 BENCHMARK(BM_NoOpReserveStringTable);
293
BM_ReserveIntTable(benchmark::State & state)294 void BM_ReserveIntTable(benchmark::State& state) {
295 int reserve_size = state.range(0);
296 for (auto _ : state) {
297 state.PauseTiming();
298 IntTable t;
299 state.ResumeTiming();
300 benchmark::DoNotOptimize(t);
301 t.reserve(reserve_size);
302 }
303 }
304 BENCHMARK(BM_ReserveIntTable)->Range(128, 4096);
305
BM_ReserveStringTable(benchmark::State & state)306 void BM_ReserveStringTable(benchmark::State& state) {
307 int reserve_size = state.range(0);
308 for (auto _ : state) {
309 state.PauseTiming();
310 StringTable t;
311 state.ResumeTiming();
312 benchmark::DoNotOptimize(t);
313 t.reserve(reserve_size);
314 }
315 }
316 BENCHMARK(BM_ReserveStringTable)->Range(128, 4096);
317
BM_Group_Match(benchmark::State & state)318 void BM_Group_Match(benchmark::State& state) {
319 std::array<ctrl_t, Group::kWidth> group;
320 std::iota(group.begin(), group.end(), -4);
321 Group g{group.data()};
322 h2_t h = 1;
323 for (auto _ : state) {
324 ::benchmark::DoNotOptimize(h);
325 ::benchmark::DoNotOptimize(g.Match(h));
326 }
327 }
328 BENCHMARK(BM_Group_Match);
329
BM_Group_MatchEmpty(benchmark::State & state)330 void BM_Group_MatchEmpty(benchmark::State& state) {
331 std::array<ctrl_t, Group::kWidth> group;
332 std::iota(group.begin(), group.end(), -4);
333 Group g{group.data()};
334 for (auto _ : state) ::benchmark::DoNotOptimize(g.MatchEmpty());
335 }
336 BENCHMARK(BM_Group_MatchEmpty);
337
BM_Group_MatchEmptyOrDeleted(benchmark::State & state)338 void BM_Group_MatchEmptyOrDeleted(benchmark::State& state) {
339 std::array<ctrl_t, Group::kWidth> group;
340 std::iota(group.begin(), group.end(), -4);
341 Group g{group.data()};
342 for (auto _ : state) ::benchmark::DoNotOptimize(g.MatchEmptyOrDeleted());
343 }
344 BENCHMARK(BM_Group_MatchEmptyOrDeleted);
345
BM_Group_CountLeadingEmptyOrDeleted(benchmark::State & state)346 void BM_Group_CountLeadingEmptyOrDeleted(benchmark::State& state) {
347 std::array<ctrl_t, Group::kWidth> group;
348 std::iota(group.begin(), group.end(), -2);
349 Group g{group.data()};
350 for (auto _ : state)
351 ::benchmark::DoNotOptimize(g.CountLeadingEmptyOrDeleted());
352 }
353 BENCHMARK(BM_Group_CountLeadingEmptyOrDeleted);
354
BM_Group_MatchFirstEmptyOrDeleted(benchmark::State & state)355 void BM_Group_MatchFirstEmptyOrDeleted(benchmark::State& state) {
356 std::array<ctrl_t, Group::kWidth> group;
357 std::iota(group.begin(), group.end(), -2);
358 Group g{group.data()};
359 for (auto _ : state) ::benchmark::DoNotOptimize(*g.MatchEmptyOrDeleted());
360 }
361 BENCHMARK(BM_Group_MatchFirstEmptyOrDeleted);
362
BM_DropDeletes(benchmark::State & state)363 void BM_DropDeletes(benchmark::State& state) {
364 constexpr size_t capacity = (1 << 20) - 1;
365 std::vector<ctrl_t> ctrl(capacity + 1 + Group::kWidth);
366 ctrl[capacity] = kSentinel;
367 std::vector<ctrl_t> pattern = {kEmpty, 2, kDeleted, 2, kEmpty, 1, kDeleted};
368 for (size_t i = 0; i != capacity; ++i) {
369 ctrl[i] = pattern[i % pattern.size()];
370 }
371 while (state.KeepRunning()) {
372 state.PauseTiming();
373 std::vector<ctrl_t> ctrl_copy = ctrl;
374 state.ResumeTiming();
375 ConvertDeletedToEmptyAndFullToDeleted(ctrl_copy.data(), capacity);
376 ::benchmark::DoNotOptimize(ctrl_copy[capacity]);
377 }
378 }
379 BENCHMARK(BM_DropDeletes);
380
381 } // namespace
382 } // namespace container_internal
383 ABSL_NAMESPACE_END
384 } // namespace absl
385
386 // These methods are here to make it easy to examine the assembly for targeted
387 // parts of the API.
CodegenAbslRawHashSetInt64Find(absl::container_internal::IntTable * table,int64_t key)388 auto CodegenAbslRawHashSetInt64Find(absl::container_internal::IntTable* table,
389 int64_t key) -> decltype(table->find(key)) {
390 return table->find(key);
391 }
392
CodegenAbslRawHashSetInt64FindNeEnd(absl::container_internal::IntTable * table,int64_t key)393 bool CodegenAbslRawHashSetInt64FindNeEnd(
394 absl::container_internal::IntTable* table, int64_t key) {
395 return table->find(key) != table->end();
396 }
397
CodegenAbslRawHashSetInt64Insert(absl::container_internal::IntTable * table,int64_t key)398 auto CodegenAbslRawHashSetInt64Insert(absl::container_internal::IntTable* table,
399 int64_t key)
400 -> decltype(table->insert(key)) {
401 return table->insert(key);
402 }
403
CodegenAbslRawHashSetInt64Contains(absl::container_internal::IntTable * table,int64_t key)404 bool CodegenAbslRawHashSetInt64Contains(
405 absl::container_internal::IntTable* table, int64_t key) {
406 return table->contains(key);
407 }
408
CodegenAbslRawHashSetInt64Iterate(absl::container_internal::IntTable * table)409 void CodegenAbslRawHashSetInt64Iterate(
410 absl::container_internal::IntTable* table) {
411 for (auto x : *table) benchmark::DoNotOptimize(x);
412 }
413
414 int odr =
415 (::benchmark::DoNotOptimize(std::make_tuple(
416 &CodegenAbslRawHashSetInt64Find, &CodegenAbslRawHashSetInt64FindNeEnd,
417 &CodegenAbslRawHashSetInt64Insert,
418 &CodegenAbslRawHashSetInt64Contains,
419 &CodegenAbslRawHashSetInt64Iterate)),
420 1);
421