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 <algorithm>
16 #include <array>
17 #include <cmath>
18 #include <cstddef>
19 #include <cstdint>
20 #include <limits>
21 #include <numeric>
22 #include <random>
23 #include <string>
24 #include <tuple>
25 #include <utility>
26 #include <vector>
27
28 #include "absl/base/internal/raw_logging.h"
29 #include "absl/container/internal/container_memory.h"
30 #include "absl/container/internal/hash_function_defaults.h"
31 #include "absl/container/internal/raw_hash_set.h"
32 #include "absl/random/random.h"
33 #include "absl/strings/str_format.h"
34 #include "benchmark/benchmark.h"
35
36 namespace absl {
37 ABSL_NAMESPACE_BEGIN
38 namespace container_internal {
39
40 struct RawHashSetTestOnlyAccess {
41 template <typename C>
GetSlotsabsl::container_internal::RawHashSetTestOnlyAccess42 static auto GetSlots(const C& c) -> decltype(c.slots_) {
43 return c.slots_;
44 }
45 };
46
47 namespace {
48
49 struct IntPolicy {
50 using slot_type = int64_t;
51 using key_type = int64_t;
52 using init_type = int64_t;
53
constructabsl::container_internal::__anon6fa6143b0111::IntPolicy54 static void construct(void*, int64_t* slot, int64_t v) { *slot = v; }
destroyabsl::container_internal::__anon6fa6143b0111::IntPolicy55 static void destroy(void*, int64_t*) {}
transferabsl::container_internal::__anon6fa6143b0111::IntPolicy56 static void transfer(void*, int64_t* new_slot, int64_t* old_slot) {
57 *new_slot = *old_slot;
58 }
59
elementabsl::container_internal::__anon6fa6143b0111::IntPolicy60 static int64_t& element(slot_type* slot) { return *slot; }
61
62 template <class F>
applyabsl::container_internal::__anon6fa6143b0111::IntPolicy63 static auto apply(F&& f, int64_t x) -> decltype(std::forward<F>(f)(x, x)) {
64 return std::forward<F>(f)(x, x);
65 }
66
67 template <class Hash>
get_hash_slot_fnabsl::container_internal::__anon6fa6143b0111::IntPolicy68 static constexpr HashSlotFn get_hash_slot_fn() {
69 return nullptr;
70 }
71 };
72
73 class StringPolicy {
74 template <class F, class K, class V,
75 class = typename std::enable_if<
76 std::is_convertible<const K&, absl::string_view>::value>::type>
77 decltype(std::declval<F>()(
78 std::declval<const absl::string_view&>(), std::piecewise_construct,
79 std::declval<std::tuple<K>>(),
apply_impl(F && f,std::pair<std::tuple<K>,V> p)80 std::declval<V>())) static apply_impl(F&& f,
81 std::pair<std::tuple<K>, V> p) {
82 const absl::string_view& key = std::get<0>(p.first);
83 return std::forward<F>(f)(key, std::piecewise_construct, std::move(p.first),
84 std::move(p.second));
85 }
86
87 public:
88 struct slot_type {
89 struct ctor {};
90
91 template <class... Ts>
slot_typeabsl::container_internal::__anon6fa6143b0111::StringPolicy::slot_type92 slot_type(ctor, Ts&&... ts) : pair(std::forward<Ts>(ts)...) {}
93
94 std::pair<std::string, std::string> pair;
95 };
96
97 using key_type = std::string;
98 using init_type = std::pair<std::string, std::string>;
99
100 template <class allocator_type, class... Args>
construct(allocator_type * alloc,slot_type * slot,Args...args)101 static void construct(allocator_type* alloc, slot_type* slot, Args... args) {
102 std::allocator_traits<allocator_type>::construct(
103 *alloc, slot, typename slot_type::ctor(), std::forward<Args>(args)...);
104 }
105
106 template <class allocator_type>
destroy(allocator_type * alloc,slot_type * slot)107 static void destroy(allocator_type* alloc, slot_type* slot) {
108 std::allocator_traits<allocator_type>::destroy(*alloc, slot);
109 }
110
111 template <class allocator_type>
transfer(allocator_type * alloc,slot_type * new_slot,slot_type * old_slot)112 static void transfer(allocator_type* alloc, slot_type* new_slot,
113 slot_type* old_slot) {
114 construct(alloc, new_slot, std::move(old_slot->pair));
115 destroy(alloc, old_slot);
116 }
117
element(slot_type * slot)118 static std::pair<std::string, std::string>& element(slot_type* slot) {
119 return slot->pair;
120 }
121
122 template <class F, class... Args>
apply(F && f,Args &&...args)123 static auto apply(F&& f, Args&&... args)
124 -> decltype(apply_impl(std::forward<F>(f),
125 PairArgs(std::forward<Args>(args)...))) {
126 return apply_impl(std::forward<F>(f),
127 PairArgs(std::forward<Args>(args)...));
128 }
129
130 template <class Hash>
get_hash_slot_fn()131 static constexpr HashSlotFn get_hash_slot_fn() {
132 return nullptr;
133 }
134 };
135
136 struct StringHash : container_internal::hash_default_hash<absl::string_view> {
137 using is_transparent = void;
138 };
139 struct StringEq : std::equal_to<absl::string_view> {
140 using is_transparent = void;
141 };
142
143 struct StringTable
144 : raw_hash_set<StringPolicy, StringHash, StringEq, std::allocator<int>> {
145 using Base = typename StringTable::raw_hash_set;
StringTableabsl::container_internal::__anon6fa6143b0111::StringTable146 StringTable() {}
147 using Base::Base;
148 };
149
150 struct IntTable
151 : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>,
152 std::equal_to<int64_t>, std::allocator<int64_t>> {
153 using Base = typename IntTable::raw_hash_set;
IntTableabsl::container_internal::__anon6fa6143b0111::IntTable154 IntTable() {}
155 using Base::Base;
156 };
157
158 struct string_generator {
159 template <class RNG>
operator ()absl::container_internal::__anon6fa6143b0111::string_generator160 std::string operator()(RNG& rng) const {
161 std::string res;
162 res.resize(size);
163 std::uniform_int_distribution<uint32_t> printable_ascii(0x20, 0x7E);
164 std::generate(res.begin(), res.end(), [&] { return printable_ascii(rng); });
165 return res;
166 }
167
168 size_t size;
169 };
170
171 // Model a cache in steady state.
172 //
173 // On a table of size N, keep deleting the LRU entry and add a random one.
BM_CacheInSteadyState(benchmark::State & state)174 void BM_CacheInSteadyState(benchmark::State& state) {
175 std::random_device rd;
176 std::mt19937 rng(rd());
177 string_generator gen{12};
178 StringTable t;
179 std::deque<std::string> keys;
180 while (t.size() < state.range(0)) {
181 auto x = t.emplace(gen(rng), gen(rng));
182 if (x.second) keys.push_back(x.first->first);
183 }
184 ABSL_RAW_CHECK(state.range(0) >= 10, "");
185 while (state.KeepRunning()) {
186 // Some cache hits.
187 std::deque<std::string>::const_iterator it;
188 for (int i = 0; i != 90; ++i) {
189 if (i % 10 == 0) it = keys.end();
190 ::benchmark::DoNotOptimize(t.find(*--it));
191 }
192 // Some cache misses.
193 for (int i = 0; i != 10; ++i) ::benchmark::DoNotOptimize(t.find(gen(rng)));
194 ABSL_RAW_CHECK(t.erase(keys.front()), keys.front().c_str());
195 keys.pop_front();
196 while (true) {
197 auto x = t.emplace(gen(rng), gen(rng));
198 if (x.second) {
199 keys.push_back(x.first->first);
200 break;
201 }
202 }
203 }
204 state.SetItemsProcessed(state.iterations());
205 state.SetLabel(absl::StrFormat("load_factor=%.2f", t.load_factor()));
206 }
207
208 template <typename Benchmark>
CacheInSteadyStateArgs(Benchmark * bm)209 void CacheInSteadyStateArgs(Benchmark* bm) {
210 // The default.
211 const float max_load_factor = 0.875;
212 // When the cache is at the steady state, the probe sequence will equal
213 // capacity if there is no reclamation of deleted slots. Pick a number large
214 // enough to make the benchmark slow for that case.
215 const size_t capacity = 1 << 10;
216
217 // Check N data points to cover load factors in [0.4, 0.8).
218 const size_t kNumPoints = 10;
219 for (size_t i = 0; i != kNumPoints; ++i)
220 bm->Arg(std::ceil(
221 capacity * (max_load_factor + i * max_load_factor / kNumPoints) / 2));
222 }
223 BENCHMARK(BM_CacheInSteadyState)->Apply(CacheInSteadyStateArgs);
224
BM_EraseEmplace(benchmark::State & state)225 void BM_EraseEmplace(benchmark::State& state) {
226 IntTable t;
227 int64_t size = state.range(0);
228 for (int64_t i = 0; i < size; ++i) {
229 t.emplace(i);
230 }
231 while (state.KeepRunningBatch(size)) {
232 for (int64_t i = 0; i < size; ++i) {
233 benchmark::DoNotOptimize(t);
234 t.erase(i);
235 t.emplace(i);
236 }
237 }
238 }
239 BENCHMARK(BM_EraseEmplace)->Arg(1)->Arg(2)->Arg(4)->Arg(8)->Arg(16)->Arg(100);
240
BM_EndComparison(benchmark::State & state)241 void BM_EndComparison(benchmark::State& state) {
242 StringTable t = {{"a", "a"}, {"b", "b"}};
243 auto it = t.begin();
244 for (auto i : state) {
245 benchmark::DoNotOptimize(t);
246 benchmark::DoNotOptimize(it);
247 benchmark::DoNotOptimize(it != t.end());
248 }
249 }
250 BENCHMARK(BM_EndComparison);
251
BM_Iteration(benchmark::State & state)252 void BM_Iteration(benchmark::State& state) {
253 std::random_device rd;
254 std::mt19937 rng(rd());
255 string_generator gen{12};
256 StringTable t;
257
258 size_t capacity = state.range(0);
259 size_t size = state.range(1);
260 t.reserve(capacity);
261
262 while (t.size() < size) {
263 t.emplace(gen(rng), gen(rng));
264 }
265
266 for (auto i : state) {
267 benchmark::DoNotOptimize(t);
268 for (auto it = t.begin(); it != t.end(); ++it) {
269 benchmark::DoNotOptimize(*it);
270 }
271 }
272 }
273
274 BENCHMARK(BM_Iteration)
275 ->ArgPair(1, 1)
276 ->ArgPair(2, 2)
277 ->ArgPair(4, 4)
278 ->ArgPair(7, 7)
279 ->ArgPair(10, 10)
280 ->ArgPair(15, 15)
281 ->ArgPair(16, 16)
282 ->ArgPair(54, 54)
283 ->ArgPair(100, 100)
284 ->ArgPair(400, 400)
285 // empty
286 ->ArgPair(0, 0)
287 ->ArgPair(10, 0)
288 ->ArgPair(100, 0)
289 ->ArgPair(1000, 0)
290 ->ArgPair(10000, 0)
291 // sparse
292 ->ArgPair(100, 1)
293 ->ArgPair(1000, 10);
294
BM_CopyCtorSparseInt(benchmark::State & state)295 void BM_CopyCtorSparseInt(benchmark::State& state) {
296 std::random_device rd;
297 std::mt19937 rng(rd());
298 IntTable t;
299 std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
300
301 size_t size = state.range(0);
302 t.reserve(size * 10);
303 while (t.size() < size) {
304 t.emplace(dist(rng));
305 }
306
307 for (auto i : state) {
308 IntTable t2 = t;
309 benchmark::DoNotOptimize(t2);
310 }
311 }
312 BENCHMARK(BM_CopyCtorSparseInt)->Range(1, 4096);
313
BM_CopyCtorInt(benchmark::State & state)314 void BM_CopyCtorInt(benchmark::State& state) {
315 std::random_device rd;
316 std::mt19937 rng(rd());
317 IntTable t;
318 std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
319
320 size_t size = state.range(0);
321 while (t.size() < size) {
322 t.emplace(dist(rng));
323 }
324
325 for (auto i : state) {
326 IntTable t2 = t;
327 benchmark::DoNotOptimize(t2);
328 }
329 }
330 BENCHMARK(BM_CopyCtorInt)->Range(0, 4096);
331
BM_CopyCtorString(benchmark::State & state)332 void BM_CopyCtorString(benchmark::State& state) {
333 std::random_device rd;
334 std::mt19937 rng(rd());
335 StringTable t;
336 std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
337
338 size_t size = state.range(0);
339 while (t.size() < size) {
340 t.emplace(std::to_string(dist(rng)), std::to_string(dist(rng)));
341 }
342
343 for (auto i : state) {
344 StringTable t2 = t;
345 benchmark::DoNotOptimize(t2);
346 }
347 }
348 BENCHMARK(BM_CopyCtorString)->Range(0, 4096);
349
BM_CopyAssign(benchmark::State & state)350 void BM_CopyAssign(benchmark::State& state) {
351 std::random_device rd;
352 std::mt19937 rng(rd());
353 IntTable t;
354 std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
355 while (t.size() < state.range(0)) {
356 t.emplace(dist(rng));
357 }
358
359 IntTable t2;
360 for (auto _ : state) {
361 t2 = t;
362 benchmark::DoNotOptimize(t2);
363 }
364 }
365 BENCHMARK(BM_CopyAssign)->Range(128, 4096);
366
BM_RangeCtor(benchmark::State & state)367 void BM_RangeCtor(benchmark::State& state) {
368 std::random_device rd;
369 std::mt19937 rng(rd());
370 std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
371 std::vector<int> values;
372 const size_t desired_size = state.range(0);
373 while (values.size() < desired_size) {
374 values.emplace_back(dist(rng));
375 }
376
377 for (auto unused : state) {
378 IntTable t{values.begin(), values.end()};
379 benchmark::DoNotOptimize(t);
380 }
381 }
382 BENCHMARK(BM_RangeCtor)->Range(128, 65536);
383
BM_NoOpReserveIntTable(benchmark::State & state)384 void BM_NoOpReserveIntTable(benchmark::State& state) {
385 IntTable t;
386 t.reserve(100000);
387 for (auto _ : state) {
388 benchmark::DoNotOptimize(t);
389 t.reserve(100000);
390 }
391 }
392 BENCHMARK(BM_NoOpReserveIntTable);
393
BM_NoOpReserveStringTable(benchmark::State & state)394 void BM_NoOpReserveStringTable(benchmark::State& state) {
395 StringTable t;
396 t.reserve(100000);
397 for (auto _ : state) {
398 benchmark::DoNotOptimize(t);
399 t.reserve(100000);
400 }
401 }
402 BENCHMARK(BM_NoOpReserveStringTable);
403
BM_ReserveIntTable(benchmark::State & state)404 void BM_ReserveIntTable(benchmark::State& state) {
405 constexpr size_t kBatchSize = 1024;
406 size_t reserve_size = static_cast<size_t>(state.range(0));
407
408 std::vector<IntTable> tables;
409 while (state.KeepRunningBatch(kBatchSize)) {
410 state.PauseTiming();
411 tables.clear();
412 tables.resize(kBatchSize);
413 state.ResumeTiming();
414 for (auto& t : tables) {
415 benchmark::DoNotOptimize(t);
416 t.reserve(reserve_size);
417 benchmark::DoNotOptimize(t);
418 }
419 }
420 }
421 BENCHMARK(BM_ReserveIntTable)->Range(1, 64);
422
BM_ReserveStringTable(benchmark::State & state)423 void BM_ReserveStringTable(benchmark::State& state) {
424 constexpr size_t kBatchSize = 1024;
425 size_t reserve_size = static_cast<size_t>(state.range(0));
426
427 std::vector<StringTable> tables;
428 while (state.KeepRunningBatch(kBatchSize)) {
429 state.PauseTiming();
430 tables.clear();
431 tables.resize(kBatchSize);
432 state.ResumeTiming();
433 for (auto& t : tables) {
434 benchmark::DoNotOptimize(t);
435 t.reserve(reserve_size);
436 benchmark::DoNotOptimize(t);
437 }
438 }
439 }
440 BENCHMARK(BM_ReserveStringTable)->Range(1, 64);
441
442 // Like std::iota, except that ctrl_t doesn't support operator++.
443 template <typename CtrlIter>
Iota(CtrlIter begin,CtrlIter end,int value)444 void Iota(CtrlIter begin, CtrlIter end, int value) {
445 for (; begin != end; ++begin, ++value) {
446 *begin = static_cast<ctrl_t>(value);
447 }
448 }
449
BM_Group_Match(benchmark::State & state)450 void BM_Group_Match(benchmark::State& state) {
451 std::array<ctrl_t, Group::kWidth> group;
452 Iota(group.begin(), group.end(), -4);
453 Group g{group.data()};
454 h2_t h = 1;
455 for (auto _ : state) {
456 ::benchmark::DoNotOptimize(h);
457 ::benchmark::DoNotOptimize(g);
458 ::benchmark::DoNotOptimize(g.Match(h));
459 }
460 }
461 BENCHMARK(BM_Group_Match);
462
BM_GroupPortable_Match(benchmark::State & state)463 void BM_GroupPortable_Match(benchmark::State& state) {
464 std::array<ctrl_t, GroupPortableImpl::kWidth> group;
465 Iota(group.begin(), group.end(), -4);
466 GroupPortableImpl g{group.data()};
467 h2_t h = 1;
468 for (auto _ : state) {
469 ::benchmark::DoNotOptimize(h);
470 ::benchmark::DoNotOptimize(g);
471 ::benchmark::DoNotOptimize(g.Match(h));
472 }
473 }
474 BENCHMARK(BM_GroupPortable_Match);
475
BM_Group_MaskEmpty(benchmark::State & state)476 void BM_Group_MaskEmpty(benchmark::State& state) {
477 std::array<ctrl_t, Group::kWidth> group;
478 Iota(group.begin(), group.end(), -4);
479 Group g{group.data()};
480 for (auto _ : state) {
481 ::benchmark::DoNotOptimize(g);
482 ::benchmark::DoNotOptimize(g.MaskEmpty());
483 }
484 }
485 BENCHMARK(BM_Group_MaskEmpty);
486
BM_Group_MaskEmptyOrDeleted(benchmark::State & state)487 void BM_Group_MaskEmptyOrDeleted(benchmark::State& state) {
488 std::array<ctrl_t, Group::kWidth> group;
489 Iota(group.begin(), group.end(), -4);
490 Group g{group.data()};
491 for (auto _ : state) {
492 ::benchmark::DoNotOptimize(g);
493 ::benchmark::DoNotOptimize(g.MaskEmptyOrDeleted());
494 }
495 }
496 BENCHMARK(BM_Group_MaskEmptyOrDeleted);
497
BM_Group_MaskNonFull(benchmark::State & state)498 void BM_Group_MaskNonFull(benchmark::State& state) {
499 std::array<ctrl_t, Group::kWidth> group;
500 Iota(group.begin(), group.end(), -4);
501 Group g{group.data()};
502 for (auto _ : state) {
503 ::benchmark::DoNotOptimize(g);
504 ::benchmark::DoNotOptimize(g.MaskNonFull());
505 }
506 }
507 BENCHMARK(BM_Group_MaskNonFull);
508
BM_Group_CountLeadingEmptyOrDeleted(benchmark::State & state)509 void BM_Group_CountLeadingEmptyOrDeleted(benchmark::State& state) {
510 std::array<ctrl_t, Group::kWidth> group;
511 Iota(group.begin(), group.end(), -2);
512 Group g{group.data()};
513 for (auto _ : state) {
514 ::benchmark::DoNotOptimize(g);
515 ::benchmark::DoNotOptimize(g.CountLeadingEmptyOrDeleted());
516 }
517 }
518 BENCHMARK(BM_Group_CountLeadingEmptyOrDeleted);
519
BM_Group_MatchFirstEmptyOrDeleted(benchmark::State & state)520 void BM_Group_MatchFirstEmptyOrDeleted(benchmark::State& state) {
521 std::array<ctrl_t, Group::kWidth> group;
522 Iota(group.begin(), group.end(), -2);
523 Group g{group.data()};
524 for (auto _ : state) {
525 ::benchmark::DoNotOptimize(g);
526 ::benchmark::DoNotOptimize(g.MaskEmptyOrDeleted().LowestBitSet());
527 }
528 }
529 BENCHMARK(BM_Group_MatchFirstEmptyOrDeleted);
530
BM_Group_MatchFirstNonFull(benchmark::State & state)531 void BM_Group_MatchFirstNonFull(benchmark::State& state) {
532 std::array<ctrl_t, Group::kWidth> group;
533 Iota(group.begin(), group.end(), -2);
534 Group g{group.data()};
535 for (auto _ : state) {
536 ::benchmark::DoNotOptimize(g);
537 ::benchmark::DoNotOptimize(g.MaskNonFull().LowestBitSet());
538 }
539 }
540 BENCHMARK(BM_Group_MatchFirstNonFull);
541
BM_DropDeletes(benchmark::State & state)542 void BM_DropDeletes(benchmark::State& state) {
543 constexpr size_t capacity = (1 << 20) - 1;
544 std::vector<ctrl_t> ctrl(capacity + 1 + Group::kWidth);
545 ctrl[capacity] = ctrl_t::kSentinel;
546 std::vector<ctrl_t> pattern = {ctrl_t::kEmpty, static_cast<ctrl_t>(2),
547 ctrl_t::kDeleted, static_cast<ctrl_t>(2),
548 ctrl_t::kEmpty, static_cast<ctrl_t>(1),
549 ctrl_t::kDeleted};
550 for (size_t i = 0; i != capacity; ++i) {
551 ctrl[i] = pattern[i % pattern.size()];
552 }
553 while (state.KeepRunning()) {
554 state.PauseTiming();
555 std::vector<ctrl_t> ctrl_copy = ctrl;
556 state.ResumeTiming();
557 ConvertDeletedToEmptyAndFullToDeleted(ctrl_copy.data(), capacity);
558 ::benchmark::DoNotOptimize(ctrl_copy[capacity]);
559 }
560 }
561 BENCHMARK(BM_DropDeletes);
562
BM_Resize(benchmark::State & state)563 void BM_Resize(benchmark::State& state) {
564 // For now just measure a small cheap hash table since we
565 // are mostly interested in the overhead of type-erasure
566 // in resize().
567 constexpr int kElements = 64;
568 const int kCapacity = kElements * 2;
569
570 IntTable table;
571 for (int i = 0; i < kElements; i++) {
572 table.insert(i);
573 }
574 for (auto unused : state) {
575 table.rehash(0);
576 table.rehash(kCapacity);
577 }
578 }
579 BENCHMARK(BM_Resize);
580
BM_EraseIf(benchmark::State & state)581 void BM_EraseIf(benchmark::State& state) {
582 int64_t num_elements = state.range(0);
583 size_t num_erased = static_cast<size_t>(state.range(1));
584
585 constexpr size_t kRepetitions = 64;
586
587 absl::BitGen rng;
588
589 std::vector<std::vector<int64_t>> keys(kRepetitions);
590 std::vector<IntTable> tables;
591 std::vector<int64_t> threshold;
592 for (auto& k : keys) {
593 tables.push_back(IntTable());
594 auto& table = tables.back();
595 for (int64_t i = 0; i < num_elements; i++) {
596 // We use random keys to reduce noise.
597 k.push_back(
598 absl::Uniform<int64_t>(rng, 0, std::numeric_limits<int64_t>::max()));
599 if (!table.insert(k.back()).second) {
600 k.pop_back();
601 --i; // duplicated value, retrying
602 }
603 }
604 std::sort(k.begin(), k.end());
605 threshold.push_back(static_cast<int64_t>(num_erased) < num_elements
606 ? k[num_erased]
607 : std::numeric_limits<int64_t>::max());
608 }
609
610 while (state.KeepRunningBatch(static_cast<int64_t>(kRepetitions) *
611 std::max(num_elements, int64_t{1}))) {
612 benchmark::DoNotOptimize(tables);
613 for (size_t t_id = 0; t_id < kRepetitions; t_id++) {
614 auto& table = tables[t_id];
615 benchmark::DoNotOptimize(num_erased);
616 auto pred = [t = threshold[t_id]](int64_t key) { return key < t; };
617 benchmark::DoNotOptimize(pred);
618 benchmark::DoNotOptimize(table);
619 absl::container_internal::EraseIf(pred, &table);
620 }
621 state.PauseTiming();
622 for (size_t t_id = 0; t_id < kRepetitions; t_id++) {
623 auto& k = keys[t_id];
624 auto& table = tables[t_id];
625 for (size_t i = 0; i < num_erased; i++) {
626 table.insert(k[i]);
627 }
628 }
629 state.ResumeTiming();
630 }
631 }
632
633 BENCHMARK(BM_EraseIf)
634 ->ArgNames({"num_elements", "num_erased"})
635 ->ArgPair(10, 0)
636 ->ArgPair(1000, 0)
637 ->ArgPair(10, 5)
638 ->ArgPair(1000, 500)
639 ->ArgPair(10, 10)
640 ->ArgPair(1000, 1000);
641
642 } // namespace
643 } // namespace container_internal
644 ABSL_NAMESPACE_END
645 } // namespace absl
646
647 // These methods are here to make it easy to examine the assembly for targeted
648 // parts of the API.
CodegenAbslRawHashSetInt64Find(absl::container_internal::IntTable * table,int64_t key)649 auto CodegenAbslRawHashSetInt64Find(absl::container_internal::IntTable* table,
650 int64_t key) -> decltype(table->find(key)) {
651 return table->find(key);
652 }
653
CodegenAbslRawHashSetInt64FindNeEnd(absl::container_internal::IntTable * table,int64_t key)654 bool CodegenAbslRawHashSetInt64FindNeEnd(
655 absl::container_internal::IntTable* table, int64_t key) {
656 return table->find(key) != table->end();
657 }
658
659 // This is useful because the find isn't inlined but the iterator comparison is.
CodegenAbslRawHashSetStringFindNeEnd(absl::container_internal::StringTable * table,const std::string & key)660 bool CodegenAbslRawHashSetStringFindNeEnd(
661 absl::container_internal::StringTable* table, const std::string& key) {
662 return table->find(key) != table->end();
663 }
664
CodegenAbslRawHashSetInt64Insert(absl::container_internal::IntTable * table,int64_t key)665 auto CodegenAbslRawHashSetInt64Insert(absl::container_internal::IntTable* table,
666 int64_t key)
667 -> decltype(table->insert(key)) {
668 return table->insert(key);
669 }
670
CodegenAbslRawHashSetInt64Contains(absl::container_internal::IntTable * table,int64_t key)671 bool CodegenAbslRawHashSetInt64Contains(
672 absl::container_internal::IntTable* table, int64_t key) {
673 return table->contains(key);
674 }
675
CodegenAbslRawHashSetInt64Iterate(absl::container_internal::IntTable * table)676 void CodegenAbslRawHashSetInt64Iterate(
677 absl::container_internal::IntTable* table) {
678 for (auto x : *table) benchmark::DoNotOptimize(x);
679 }
680
681 int odr =
682 (::benchmark::DoNotOptimize(std::make_tuple(
683 &CodegenAbslRawHashSetInt64Find, &CodegenAbslRawHashSetInt64FindNeEnd,
684 &CodegenAbslRawHashSetStringFindNeEnd,
685 &CodegenAbslRawHashSetInt64Insert, &CodegenAbslRawHashSetInt64Contains,
686 &CodegenAbslRawHashSetInt64Iterate)),
687 1);
688