xref: /aosp_15_r20/external/abseil-cpp/absl/container/internal/raw_hash_set_benchmark.cc (revision 9356374a3709195abf420251b3e825997ff56c0f)
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