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::__anon34bf2df10111::IntPolicy43 static void construct(void*, int64_t* slot, int64_t v) { *slot = v; }
destroyabsl::container_internal::__anon34bf2df10111::IntPolicy44 static void destroy(void*, int64_t*) {}
transferabsl::container_internal::__anon34bf2df10111::IntPolicy45 static void transfer(void*, int64_t* new_slot, int64_t* old_slot) {
46 *new_slot = *old_slot;
47 }
48
elementabsl::container_internal::__anon34bf2df10111::IntPolicy49 static int64_t& element(slot_type* slot) { return *slot; }
50
51 template <class F>
applyabsl::container_internal::__anon34bf2df10111::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::__anon34bf2df10111::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::__anon34bf2df10111::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::__anon34bf2df10111::IntTable133 IntTable() {}
134 using Base::Base;
135 };
136
137 struct string_generator {
138 template <class RNG>
operator ()absl::container_internal::__anon34bf2df10111::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
318 // Like std::iota, except that ctrl_t doesn't support operator++.
319 template <typename CtrlIter>
Iota(CtrlIter begin,CtrlIter end,int value)320 void Iota(CtrlIter begin, CtrlIter end, int value) {
321 for (; begin != end; ++begin, ++value) {
322 *begin = static_cast<ctrl_t>(value);
323 }
324 }
325
BM_Group_Match(benchmark::State & state)326 void BM_Group_Match(benchmark::State& state) {
327 std::array<ctrl_t, Group::kWidth> group;
328 Iota(group.begin(), group.end(), -4);
329 Group g{group.data()};
330 h2_t h = 1;
331 for (auto _ : state) {
332 ::benchmark::DoNotOptimize(h);
333 ::benchmark::DoNotOptimize(g.Match(h));
334 }
335 }
336 BENCHMARK(BM_Group_Match);
337
BM_Group_MatchEmpty(benchmark::State & state)338 void BM_Group_MatchEmpty(benchmark::State& state) {
339 std::array<ctrl_t, Group::kWidth> group;
340 Iota(group.begin(), group.end(), -4);
341 Group g{group.data()};
342 for (auto _ : state) ::benchmark::DoNotOptimize(g.MatchEmpty());
343 }
344 BENCHMARK(BM_Group_MatchEmpty);
345
BM_Group_MatchEmptyOrDeleted(benchmark::State & state)346 void BM_Group_MatchEmptyOrDeleted(benchmark::State& state) {
347 std::array<ctrl_t, Group::kWidth> group;
348 Iota(group.begin(), group.end(), -4);
349 Group g{group.data()};
350 for (auto _ : state) ::benchmark::DoNotOptimize(g.MatchEmptyOrDeleted());
351 }
352 BENCHMARK(BM_Group_MatchEmptyOrDeleted);
353
BM_Group_CountLeadingEmptyOrDeleted(benchmark::State & state)354 void BM_Group_CountLeadingEmptyOrDeleted(benchmark::State& state) {
355 std::array<ctrl_t, Group::kWidth> group;
356 Iota(group.begin(), group.end(), -2);
357 Group g{group.data()};
358 for (auto _ : state)
359 ::benchmark::DoNotOptimize(g.CountLeadingEmptyOrDeleted());
360 }
361 BENCHMARK(BM_Group_CountLeadingEmptyOrDeleted);
362
BM_Group_MatchFirstEmptyOrDeleted(benchmark::State & state)363 void BM_Group_MatchFirstEmptyOrDeleted(benchmark::State& state) {
364 std::array<ctrl_t, Group::kWidth> group;
365 Iota(group.begin(), group.end(), -2);
366 Group g{group.data()};
367 for (auto _ : state) ::benchmark::DoNotOptimize(*g.MatchEmptyOrDeleted());
368 }
369 BENCHMARK(BM_Group_MatchFirstEmptyOrDeleted);
370
BM_DropDeletes(benchmark::State & state)371 void BM_DropDeletes(benchmark::State& state) {
372 constexpr size_t capacity = (1 << 20) - 1;
373 std::vector<ctrl_t> ctrl(capacity + 1 + Group::kWidth);
374 ctrl[capacity] = ctrl_t::kSentinel;
375 std::vector<ctrl_t> pattern = {ctrl_t::kEmpty, static_cast<ctrl_t>(2),
376 ctrl_t::kDeleted, static_cast<ctrl_t>(2),
377 ctrl_t::kEmpty, static_cast<ctrl_t>(1),
378 ctrl_t::kDeleted};
379 for (size_t i = 0; i != capacity; ++i) {
380 ctrl[i] = pattern[i % pattern.size()];
381 }
382 while (state.KeepRunning()) {
383 state.PauseTiming();
384 std::vector<ctrl_t> ctrl_copy = ctrl;
385 state.ResumeTiming();
386 ConvertDeletedToEmptyAndFullToDeleted(ctrl_copy.data(), capacity);
387 ::benchmark::DoNotOptimize(ctrl_copy[capacity]);
388 }
389 }
390 BENCHMARK(BM_DropDeletes);
391
392 } // namespace
393 } // namespace container_internal
394 ABSL_NAMESPACE_END
395 } // namespace absl
396
397 // These methods are here to make it easy to examine the assembly for targeted
398 // parts of the API.
CodegenAbslRawHashSetInt64Find(absl::container_internal::IntTable * table,int64_t key)399 auto CodegenAbslRawHashSetInt64Find(absl::container_internal::IntTable* table,
400 int64_t key) -> decltype(table->find(key)) {
401 return table->find(key);
402 }
403
CodegenAbslRawHashSetInt64FindNeEnd(absl::container_internal::IntTable * table,int64_t key)404 bool CodegenAbslRawHashSetInt64FindNeEnd(
405 absl::container_internal::IntTable* table, int64_t key) {
406 return table->find(key) != table->end();
407 }
408
CodegenAbslRawHashSetInt64Insert(absl::container_internal::IntTable * table,int64_t key)409 auto CodegenAbslRawHashSetInt64Insert(absl::container_internal::IntTable* table,
410 int64_t key)
411 -> decltype(table->insert(key)) {
412 return table->insert(key);
413 }
414
CodegenAbslRawHashSetInt64Contains(absl::container_internal::IntTable * table,int64_t key)415 bool CodegenAbslRawHashSetInt64Contains(
416 absl::container_internal::IntTable* table, int64_t key) {
417 return table->contains(key);
418 }
419
CodegenAbslRawHashSetInt64Iterate(absl::container_internal::IntTable * table)420 void CodegenAbslRawHashSetInt64Iterate(
421 absl::container_internal::IntTable* table) {
422 for (auto x : *table) benchmark::DoNotOptimize(x);
423 }
424
425 int odr =
426 (::benchmark::DoNotOptimize(std::make_tuple(
427 &CodegenAbslRawHashSetInt64Find, &CodegenAbslRawHashSetInt64FindNeEnd,
428 &CodegenAbslRawHashSetInt64Insert,
429 &CodegenAbslRawHashSetInt64Contains,
430 &CodegenAbslRawHashSetInt64Iterate)),
431 1);
432