• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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