/src/abseil-cpp/absl/container/internal/raw_hash_set.cc
Line | Count | Source |
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 <algorithm> |
18 | | #include <atomic> |
19 | | #include <cassert> |
20 | | #include <cstddef> |
21 | | #include <cstdint> |
22 | | #include <cstring> |
23 | | #include <memory> |
24 | | #include <tuple> |
25 | | #include <type_traits> |
26 | | #include <utility> |
27 | | |
28 | | #include "absl/base/attributes.h" |
29 | | #include "absl/base/config.h" |
30 | | #include "absl/base/dynamic_annotations.h" |
31 | | #include "absl/base/internal/endian.h" |
32 | | #include "absl/base/internal/raw_logging.h" |
33 | | #include "absl/base/optimization.h" |
34 | | #include "absl/container/internal/container_memory.h" |
35 | | #include "absl/container/internal/hashtable_control_bytes.h" |
36 | | #include "absl/container/internal/hashtablez_sampler.h" |
37 | | #include "absl/container/internal/raw_hash_set_resize_impl.h" |
38 | | #include "absl/functional/function_ref.h" |
39 | | #include "absl/hash/hash.h" |
40 | | |
41 | | namespace absl { |
42 | | ABSL_NAMESPACE_BEGIN |
43 | | namespace container_internal { |
44 | | |
45 | | // Represents a control byte corresponding to a full slot with arbitrary hash. |
46 | 0 | constexpr ctrl_t ZeroCtrlT() { return static_cast<ctrl_t>(0); } |
47 | | |
48 | | // A single byte for default-constructed iterators. We leave it uninitialized |
49 | | // because reading this memory is a bug. |
50 | | ABSL_DLL char kDefaultIterSlot; |
51 | | |
52 | | // We need one full byte followed by a sentinel byte for iterator::operator++. |
53 | | ABSL_CONST_INIT ABSL_DLL const ctrl_t kSooControl[2] = {ZeroCtrlT(), |
54 | | ctrl_t::kSentinel}; |
55 | | // We need one full byte followed by a sentinel byte for iterator::operator++. |
56 | | ABSL_CONST_INIT ABSL_DLL const ctrl_t kInsertIteratorControl[2] = { |
57 | | ZeroCtrlT(), ctrl_t::kSentinel}; |
58 | | |
59 | | namespace { |
60 | | |
61 | | #ifdef ABSL_SWISSTABLE_ASSERT |
62 | | #error ABSL_SWISSTABLE_ASSERT cannot be directly set |
63 | | #else |
64 | | // We use this macro for assertions that users may see when the table is in an |
65 | | // invalid state that sanitizers may help diagnose. |
66 | | #define ABSL_SWISSTABLE_ASSERT(CONDITION) \ |
67 | 79.8M | assert((CONDITION) && "Try enabling sanitizers.") |
68 | | #endif |
69 | | |
70 | | void ValidateMaxSize([[maybe_unused]] size_t size, |
71 | | [[maybe_unused]] size_t key_size, |
72 | 0 | [[maybe_unused]] size_t slot_size) { |
73 | 0 | ABSL_SWISSTABLE_ASSERT(size <= MaxValidSize(key_size, slot_size)); |
74 | 0 | } |
75 | 0 | void ValidateMaxCapacity(size_t capacity, size_t key_size, size_t slot_size) { |
76 | 0 | if (capacity <= 1) return; |
77 | 0 | ValidateMaxSize(CapacityToGrowth(PreviousCapacity(capacity)), key_size, |
78 | 0 | slot_size); |
79 | 0 | } |
80 | | |
81 | | // Returns "random" seed. |
82 | 285k | inline size_t RandomSeed() { |
83 | 285k | constexpr size_t kIncrement = 0xad53; |
84 | 285k | #ifdef ABSL_HAVE_THREAD_LOCAL |
85 | 285k | static thread_local size_t counter = 0; |
86 | 285k | counter += kIncrement; |
87 | 285k | size_t value = counter; |
88 | | #else // ABSL_HAVE_THREAD_LOCAL |
89 | | static std::atomic<size_t> counter(0); |
90 | | size_t value = counter.fetch_add(kIncrement, std::memory_order_relaxed); |
91 | | #endif // ABSL_HAVE_THREAD_LOCAL |
92 | 285k | return value ^ static_cast<size_t>(reinterpret_cast<uintptr_t>(&counter)); |
93 | 285k | } |
94 | | |
95 | 0 | bool ShouldRehashForBugDetection(size_t capacity) { |
96 | | // Note: we can't use the abseil-random library because abseil-random |
97 | | // depends on swisstable. We want to return true with probability |
98 | | // `min(1, RehashProbabilityConstant() / capacity())`. In order to do this, |
99 | | // we probe based on a random hash and see if the offset is less than |
100 | | // RehashProbabilityConstant(). |
101 | 0 | return probe(ProbeCapacity{capacity}, absl::HashOf(RandomSeed())) |
102 | 0 | .offset() < RehashProbabilityConstant(); |
103 | 0 | } |
104 | | |
105 | | // Returns the offset of the new element after resize from capacity 1 to 3. |
106 | 285k | size_t Resize1To3NewOffset(size_t hash) { |
107 | | // After resize from capacity 1 to 3, we always have exactly the slot with |
108 | | // index 1 occupied, so we need to insert either at index 0 or index 2. |
109 | 285k | static_assert(SooSlotIndex() == 1); |
110 | 285k | return hash & 2; |
111 | 285k | } |
112 | | |
113 | | // Returns the address of the ith slot in slots where each slot occupies |
114 | | // slot_size. |
115 | 14.1M | inline void* SlotAddress(void* slot_array, size_t slot, size_t slot_size) { |
116 | 14.1M | return static_cast<void*>(static_cast<char*>(slot_array) + |
117 | 14.1M | (slot * slot_size)); |
118 | 14.1M | } |
119 | | |
120 | | // Returns the address of the slot `i` iterations after `slot` assuming each |
121 | | // slot has the specified size. |
122 | 732k | inline void* NextSlot(void* slot, size_t slot_size, size_t i = 1) { |
123 | 732k | return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) + |
124 | 732k | slot_size * i); |
125 | 732k | } |
126 | | |
127 | | // Returns the address of the slot just before `slot` assuming each slot has the |
128 | | // specified size. |
129 | 0 | inline void* PrevSlot(void* slot, size_t slot_size) { |
130 | 0 | return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size); |
131 | 0 | } |
132 | | |
133 | | } // namespace |
134 | | |
135 | | // Must be defined out-of-line to avoid MSVC error C2482 on some platforms, |
136 | | // which is caused by non-constexpr initialization. |
137 | 285k | uint8_t NextHashTableSeed() { |
138 | 285k | return static_cast<uint8_t>(RandomSeed()); |
139 | 285k | } |
140 | | |
141 | 0 | GenerationType* EmptyGeneration() { |
142 | 0 | if (SwisstableGenerationsEnabled()) { |
143 | 0 | constexpr size_t kNumEmptyGenerations = 1024; |
144 | 0 | static constexpr GenerationType kEmptyGenerations[kNumEmptyGenerations]{}; |
145 | 0 | return const_cast<GenerationType*>( |
146 | 0 | &kEmptyGenerations[RandomSeed() % kNumEmptyGenerations]); |
147 | 0 | } |
148 | 0 | return nullptr; |
149 | 0 | } |
150 | | |
151 | | bool CommonFieldsGenerationInfoEnabled:: |
152 | 0 | should_rehash_for_bug_detection_on_insert(size_t capacity) const { |
153 | 0 | if (reserved_growth_ == kReservedGrowthJustRanOut) return true; |
154 | 0 | if (reserved_growth_ > 0) return false; |
155 | 0 | return ShouldRehashForBugDetection(capacity); |
156 | 0 | } |
157 | | |
158 | | bool CommonFieldsGenerationInfoEnabled::should_rehash_for_bug_detection_on_move( |
159 | 0 | size_t capacity) const { |
160 | 0 | return ShouldRehashForBugDetection(capacity); |
161 | 0 | } |
162 | | |
163 | | namespace { |
164 | | |
165 | | // Probes an array of control bits using a probe sequence, |
166 | | // and returns the mask corresponding to the first group with a deleted or empty |
167 | | // slot. |
168 | | inline Group::NonIterableBitMaskType probe_till_first_non_full_group( |
169 | | const ctrl_t* ctrl, probe_seq<Group::kWidth>& seq, |
170 | 313k | [[maybe_unused]] size_t capacity) { |
171 | 318k | while (true) { |
172 | 318k | GroupFullEmptyOrDeleted g{ctrl + seq.offset()}; |
173 | 318k | auto mask = g.MaskEmptyOrDeleted(); |
174 | 318k | if (mask) { |
175 | 313k | return mask; |
176 | 313k | } |
177 | 4.92k | seq.next(); |
178 | 4.92k | } |
179 | 313k | } |
180 | | |
181 | | FindInfo find_first_non_full_from_h1(size_t cap, const ctrl_t* ctrl, |
182 | 605k | size_t h1) { |
183 | 605k | auto seq = probe_h1(ProbeCapacity{cap}, h1); |
184 | 605k | if (IsEmptyOrDeleted(ctrl[seq.offset()])) { |
185 | 292k | return {seq.offset(), /*probe_length=*/0}; |
186 | 292k | } |
187 | 313k | auto mask = probe_till_first_non_full_group(ctrl, seq, cap); |
188 | 313k | return {seq.offset(mask.LowestBitSet()), seq.index()}; |
189 | 605k | } |
190 | | |
191 | | // Probes an array of control bits using a probe sequence derived from `hash`, |
192 | | // and returns the offset corresponding to the first deleted or empty slot. |
193 | | // |
194 | | // Behavior when the entire table is full is undefined. |
195 | | // |
196 | | // NOTE: this function must work with tables having both empty and deleted |
197 | | // slots in the same group. Such tables appear during `erase()`. |
198 | 387k | FindInfo find_first_non_full(size_t cap, const ctrl_t* ctrl, size_t hash) { |
199 | 387k | return find_first_non_full_from_h1(cap, ctrl, H1(hash)); |
200 | 387k | } |
201 | | |
202 | | // Same as `find_first_non_full`, but returns the mask corresponding to the |
203 | | // first group with a deleted or empty slot. |
204 | | std::pair<FindInfo, Group::NonIterableBitMaskType> find_first_non_full_group( |
205 | 0 | const CommonFields& common, size_t hash) { |
206 | 0 | auto seq = probe(common, hash); |
207 | 0 | auto mask = |
208 | 0 | probe_till_first_non_full_group(common.control(), seq, common.capacity()); |
209 | 0 | return {{seq.offset(), seq.index()}, mask}; |
210 | 0 | } |
211 | | |
212 | | // Whether a table fits in half a group. A half-group table fits entirely into a |
213 | | // probing group, i.e., has a capacity < `Group::kWidth`. |
214 | | // |
215 | | // In half-group mode we are able to use the whole capacity. The extra control |
216 | | // bytes give us at least one "empty" control byte to stop the iteration. |
217 | | // This is important to make 1 a valid capacity. |
218 | | // |
219 | | // In half-group mode only the first `capacity` control bytes after the sentinel |
220 | | // are valid. The rest contain dummy ctrl_t::kEmpty values that do not |
221 | | // represent a real slot. |
222 | 0 | constexpr bool is_half_group(size_t capacity) { |
223 | 0 | return capacity < Group::kWidth - 1; |
224 | 0 | } |
225 | | |
226 | | template <class Fn> |
227 | 0 | void IterateOverFullSlotsImpl(const CommonFields& c, size_t slot_size, Fn cb) { |
228 | 0 | const size_t cap = c.capacity(); |
229 | 0 | ABSL_ASSUME(cap > kMaxSmallCapacity); |
230 | 0 | const ctrl_t* ctrl = c.control(); |
231 | 0 | void* slot = c.slot_array(cap); |
232 | 0 | if (is_half_group(cap)) { |
233 | | // Mirrored/cloned control bytes in half-group table are also located in the |
234 | | // first group (starting from position 0). We are taking group from position |
235 | | // `capacity` in order to avoid duplicates. |
236 | | |
237 | | // Half-group tables capacity fits into portable group, where |
238 | | // GroupPortableImpl::MaskFull is more efficient for the |
239 | | // capacity <= GroupPortableImpl::kWidth. |
240 | 0 | ABSL_SWISSTABLE_ASSERT(cap <= GroupPortableImpl::kWidth && |
241 | 0 | "unexpectedly large half-group capacity"); |
242 | 0 | static_assert(Group::kWidth >= GroupPortableImpl::kWidth, |
243 | 0 | "unexpected group width"); |
244 | | // Group starts from kSentinel slot, so indices in the mask will |
245 | | // be increased by 1. |
246 | 0 | const auto mask = GroupPortableImpl(ctrl + cap).MaskFull(); |
247 | 0 | --ctrl; |
248 | 0 | slot = PrevSlot(slot, slot_size); |
249 | 0 | for (uint32_t i : mask) { |
250 | 0 | cb(ctrl + i, SlotAddress(slot, i, slot_size)); |
251 | 0 | } |
252 | 0 | return; |
253 | 0 | } |
254 | 0 | size_t remaining = c.size(); |
255 | 0 | [[maybe_unused]] const size_t original_size_for_assert = remaining; |
256 | 0 | while (remaining != 0) { |
257 | 0 | for (uint32_t i : GroupFullEmptyOrDeleted(ctrl).MaskFull()) { |
258 | 0 | ABSL_SWISSTABLE_ASSERT(IsFull(ctrl[i]) && |
259 | 0 | "hash table was modified unexpectedly"); |
260 | 0 | cb(ctrl + i, SlotAddress(slot, i, slot_size)); |
261 | 0 | --remaining; |
262 | 0 | } |
263 | 0 | ctrl += Group::kWidth; |
264 | 0 | slot = NextSlot(slot, slot_size, Group::kWidth); |
265 | 0 | ABSL_SWISSTABLE_ASSERT( |
266 | 0 | (remaining == 0 || *(ctrl - 1) != ctrl_t::kSentinel) && |
267 | 0 | "hash table was modified unexpectedly"); |
268 | 0 | } |
269 | | // NOTE: erasure of the current element is allowed in callback for |
270 | | // absl::erase_if specialization. So we use `>=`. |
271 | 0 | ABSL_SWISSTABLE_ASSERT(original_size_for_assert >= c.size() && |
272 | 0 | "hash table was modified unexpectedly"); |
273 | 0 | } Unexecuted instantiation: raw_hash_set.cc:void absl::container_internal::(anonymous namespace)::IterateOverFullSlotsImpl<absl::FunctionRef<void (absl::container_internal::ctrl_t const*, void*)> >(absl::container_internal::CommonFields const&, unsigned long, absl::FunctionRef<void (absl::container_internal::ctrl_t const*, void*)>) Unexecuted instantiation: raw_hash_set.cc:void absl::container_internal::(anonymous namespace)::IterateOverFullSlotsImpl<absl::container_internal::DestroySlots(absl::container_internal::CommonFields&, unsigned long, void (*)(void*, void*))::$_0>(absl::container_internal::CommonFields const&, unsigned long, absl::container_internal::DestroySlots(absl::container_internal::CommonFields&, unsigned long, void (*)(void*, void*))::$_0) Unexecuted instantiation: raw_hash_set.cc:void absl::container_internal::(anonymous namespace)::IterateOverFullSlotsImpl<absl::container_internal::Copy(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::CommonFields const&, void (*)(void*, void*, void const*))::$_0>(absl::container_internal::CommonFields const&, unsigned long, absl::container_internal::Copy(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::CommonFields const&, void (*)(void*, void*, void const*))::$_0) |
274 | | |
275 | | } // namespace |
276 | | |
277 | | #ifndef NDEBUG |
278 | | ReentranceGuard::ReentranceGuard(CommonFields& common) |
279 | 0 | : common_(common), capacity_(common.maybe_invalid_capacity()) { |
280 | 0 | common_.set_capacity(HashtableCapacity::CreateReentrance()); |
281 | 0 | } |
282 | | |
283 | 0 | ReentranceGuard::~ReentranceGuard() { common_.set_capacity(capacity_); } |
284 | | #endif // NDEBUG |
285 | | |
286 | 14.0k | void CommonFields::AssertInSooModeImpl() const { |
287 | 14.0k | ABSL_SWISSTABLE_ASSERT(capacity() == SooCapacity()); |
288 | 14.0k | ABSL_SWISSTABLE_ASSERT(!has_infoz()); |
289 | 14.0k | } |
290 | | |
291 | 0 | void CommonFields::AssertNotDebugCapacityImpl() const { |
292 | 0 | const HashtableCapacity cap = maybe_invalid_capacity(); |
293 | 0 | assert(!cap.IsReentrance() && |
294 | 0 | "Reentrant container access during element construction/destruction " |
295 | 0 | "is not allowed."); |
296 | 0 | if (cap.IsDestroyed()) { |
297 | 0 | ABSL_RAW_LOG(FATAL, "Use of destroyed hash table."); |
298 | 0 | } |
299 | 0 | if (SwisstableGenerationsEnabled() && ABSL_PREDICT_FALSE(cap.IsMovedFrom())) { |
300 | 0 | if (cap.IsSelfMovedFrom()) { |
301 | | // If this log triggers, then a hash table was move-assigned to itself |
302 | | // and then used again later without being reinitialized. |
303 | 0 | ABSL_RAW_LOG(FATAL, "Use of self-move-assigned hash table."); |
304 | 0 | } |
305 | 0 | ABSL_RAW_LOG(FATAL, "Use of moved-from hash table."); |
306 | 0 | } |
307 | 0 | } |
308 | | |
309 | | void CommonFields::AnnotateForBugDetectionOnMoveImpl( |
310 | 0 | CommonFields& that, const PolicyFunctions& policy) { |
311 | 0 | if (!SwisstableGenerationsEnabled()) { |
312 | 0 | return; |
313 | 0 | } |
314 | 0 | that.set_capacity(this == &that ? HashtableCapacity::CreateSelfMovedFrom() |
315 | 0 | : HashtableCapacity::CreateMovedFrom()); |
316 | 0 | if (!maybe_invalid_capacity().IsValid() || |
317 | 0 | capacity() == policy.soo_capacity()) { |
318 | 0 | return; |
319 | 0 | } |
320 | 0 | increment_generation(); |
321 | 0 | if (!empty() && should_rehash_for_bug_detection_on_move()) { |
322 | 0 | ResizeAllocatedTableWithSeedChange(*this, policy, capacity()); |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | 0 | void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity) { |
327 | 0 | ABSL_SWISSTABLE_ASSERT(ctrl[capacity] == ctrl_t::kSentinel); |
328 | 0 | ABSL_SWISSTABLE_ASSERT(IsValidCapacity(capacity)); |
329 | 0 | for (ctrl_t* pos = ctrl; pos < ctrl + capacity; pos += Group::kWidth) { |
330 | 0 | Group{pos}.ConvertSpecialToEmptyAndFullToDeleted(pos); |
331 | 0 | } |
332 | | // Copy the cloned ctrl bytes. |
333 | 0 | std::memcpy(ctrl + capacity + 1, ctrl, NumClonedBytes()); |
334 | 0 | ctrl[capacity] = ctrl_t::kSentinel; |
335 | 0 | } |
336 | | |
337 | | void IterateOverFullSlots(const CommonFields& c, size_t slot_size, |
338 | 0 | absl::FunctionRef<void(const ctrl_t*, void*)> cb) { |
339 | 0 | IterateOverFullSlotsImpl(c, slot_size, cb); |
340 | 0 | } |
341 | | |
342 | | void CommonFields::InitGrowthLeftNoDeleted(size_t growth_left, |
343 | 1.14M | size_t capacity) { |
344 | 1.14M | if (capacity <= GrowthInfoLowerBound::kMaxGrowthLeftLowerBound) { |
345 | 1.09M | inline_data_.set_growth_info_lower_bound( |
346 | 1.09M | GrowthInfoLowerBound(static_cast<uint8_t>(growth_left))); |
347 | 1.09M | } else { |
348 | 42.1k | size_t lower_bound = (std::min)( |
349 | 42.1k | growth_left, |
350 | 42.1k | GrowthInfoLowerBound::kMaxGrowthLeftLowerBound); |
351 | 42.1k | inline_data_.set_growth_info_lower_bound( |
352 | 42.1k | GrowthInfoLowerBound(static_cast<uint8_t>(lower_bound))); |
353 | 42.1k | SetGrowthInfoOverflow(growth_left - lower_bound); |
354 | 42.1k | } |
355 | 1.14M | } |
356 | | |
357 | 855k | size_t CommonFields::GetGrowthLeftTotalSlow(size_t capacity) const { |
358 | 855k | size_t result = inline_data_.growth_info_lower_bound().GetGrowthLeft(); |
359 | 855k | if (capacity > GrowthInfoLowerBound::kMaxGrowthLeftLowerBound) { |
360 | 18.7k | result += GetOverflowGrowthLeft(); |
361 | 18.7k | } |
362 | 855k | return result; |
363 | 855k | } |
364 | | |
365 | 0 | void CommonFields::OverwriteFullAsEmpty() { |
366 | 0 | if (inline_data_.growth_info_lower_bound().GetGrowthLeft() < |
367 | 0 | GrowthInfoLowerBound::kMaxGrowthLeftLowerBound) { |
368 | 0 | inline_data_.overwrite_full_as_empty_in_lower_bound(); |
369 | 0 | } else { |
370 | 0 | SetGrowthInfoOverflow(GetOverflowGrowthLeft() + 1); |
371 | 0 | } |
372 | 0 | } |
373 | | |
374 | | GrowthInfoLowerBound CommonFields::RebalanceGrowthLeftLowerBound( |
375 | 862k | size_t capacity) { |
376 | 862k | auto growth_left_lower_bound = GetGrowthInfoLowerBound(); |
377 | 862k | if (capacity <= GrowthInfoLowerBound::kMaxGrowthLeftLowerBound || |
378 | | // For tables with deleted slots, we often call rebalance even if |
379 | | // we have growth left in the lower bound. |
380 | 837k | growth_left_lower_bound.HasDeletedAndGrowthLeft()) { |
381 | 837k | return growth_left_lower_bound; |
382 | 837k | } else { |
383 | 25.3k | return RebalanceGrowthLeftLowerBoundLargeCapacity(); |
384 | 25.3k | } |
385 | 862k | } |
386 | | |
387 | | ABSL_ATTRIBUTE_NOINLINE GrowthInfoLowerBound |
388 | 25.3k | CommonFields::RebalanceGrowthLeftLowerBoundLargeCapacity() { |
389 | 25.3k | size_t overflow_growth_left = GetOverflowGrowthLeft(); |
390 | 25.3k | size_t lower_bound_growth_left = GetGrowthLeftLowerBound(); |
391 | 25.3k | size_t overflow_to_lower_bound_size = |
392 | 25.3k | (std::min)(overflow_growth_left, |
393 | 25.3k | GrowthInfoLowerBound::kMaxGrowthLeftLowerBound - |
394 | 25.3k | lower_bound_growth_left); |
395 | 25.3k | SetGrowthInfoOverflow(overflow_growth_left - overflow_to_lower_bound_size); |
396 | 25.3k | inline_data_.increment_growth_info_lower_bound(overflow_to_lower_bound_size); |
397 | 25.3k | auto result = GetGrowthInfoLowerBound(); |
398 | 25.3k | ABSL_SWISSTABLE_ASSERT(result.HasNoDeleted() == |
399 | 25.3k | GetGrowthInfoLowerBound().HasNoDeleted()); |
400 | 25.3k | ABSL_SWISSTABLE_ASSERT( |
401 | 25.3k | (result.GetGrowthLeft() > 0 || |
402 | 25.3k | (GetGrowthLeftLowerBound() + GetOverflowGrowthLeft() == 0)) && |
403 | 25.3k | "rebalance may return 0 only if we have absolutely no growth left"); |
404 | 25.3k | return result; |
405 | 25.3k | } |
406 | | |
407 | 0 | HashtablezInfoHandle CommonFields::infoz_from_control(ctrl_t* ctrl) const { |
408 | | // growth_info is stored before control bytes. |
409 | 0 | ABSL_SWISSTABLE_ASSERT(has_infoz()); |
410 | 0 | HashtablezInfoHandle res; |
411 | 0 | void* src = reinterpret_cast<char*>(ctrl) - |
412 | 0 | MetadataBeforeControlSize(/*has_infoz=*/true, capacity()); |
413 | 0 | std::memcpy(&res, src, sizeof(HashtablezInfoHandle)); |
414 | 0 | return res; |
415 | 0 | } |
416 | | |
417 | 0 | HashtablezInfoHandle CommonFields::infoz_ptr() const { |
418 | 0 | return infoz_from_control(control()); |
419 | 0 | } |
420 | | |
421 | 0 | void CommonFields::set_infoz(HashtablezInfoHandle infoz) { |
422 | 0 | ABSL_SWISSTABLE_ASSERT(has_infoz()); |
423 | 0 | void* dst = reinterpret_cast<char*>(control()) - |
424 | 0 | MetadataBeforeControlSize(/*has_infoz=*/true, capacity()); |
425 | 0 | std::memcpy(dst, &infoz, sizeof(HashtablezInfoHandle)); |
426 | 0 | } |
427 | | |
428 | | namespace { |
429 | | void DeallocBackingArrayImpl(void* alloc, size_t capacity, ctrl_t* ctrl, |
430 | | size_t slot_size, size_t slot_align_mask, |
431 | | bool has_infoz, size_t blocked_element_count, |
432 | 1.40M | DeallocBackingArrayFn dealloc) { |
433 | 1.40M | RawHashSetLayout layout(capacity, slot_size, slot_align_mask, has_infoz, |
434 | 1.40M | blocked_element_count); |
435 | 1.40M | void* backing_array = ctrl - layout.control_offset(); |
436 | | // Unpoison before returning the memory to the allocator. |
437 | 1.40M | SanitizerUnpoisonMemoryRegion(backing_array, layout.alloc_size()); |
438 | 1.40M | dealloc(alloc, backing_array, layout.alloc_size()); |
439 | 1.40M | } |
440 | | |
441 | | void DeallocBackingArrayImpl(CommonFields& c, |
442 | | const PolicyFunctions& __restrict policy, |
443 | 268k | void* alloc) { |
444 | 268k | DeallocBackingArrayImpl(alloc, c.capacity(), c.control(), policy.slot_size, |
445 | 268k | policy.slot_align_mask, c.has_infoz(), |
446 | 268k | c.blocked_element_count(), policy.dealloc); |
447 | 268k | } |
448 | | |
449 | | } // namespace |
450 | | |
451 | | void UnregisterAndDeallocBackingArray(CommonFields& c, |
452 | | const DtorPolicy& __restrict policy, |
453 | | DeallocBackingArrayFn dealloc, |
454 | 20.0k | void* alloc) { |
455 | 20.0k | size_t cap = c.capacity(); // capacity is already in register, so storing it |
456 | | // in a local variable before Unregister(). |
457 | 20.0k | c.infoz().Unregister(); |
458 | 20.0k | DeallocBackingArrayImpl(alloc, cap, c.control(), policy.slot_size, |
459 | 20.0k | policy.slot_align_mask, c.has_infoz(), |
460 | 20.0k | c.blocked_element_count(), dealloc); |
461 | 20.0k | } |
462 | | |
463 | | namespace { |
464 | | |
465 | | void ResetGrowthLeft(size_t capacity, size_t occupied_elements, |
466 | 855k | CommonFields& common) { |
467 | 855k | common.InitGrowthLeftNoDeleted(CapacityToGrowth(capacity) - occupied_elements, |
468 | 855k | capacity); |
469 | 855k | } |
470 | | |
471 | | // Finds guaranteed to exists empty slot from the given position. |
472 | | // NOTE: this function is almost never triggered inside of the |
473 | | // DropDeletesWithoutResize, so we keep it simple. |
474 | | // The table is rather sparse, so empty slot will be found very quickly. |
475 | 0 | size_t FindEmptySlot(size_t start, size_t end, const ctrl_t* ctrl) { |
476 | 0 | for (size_t i = start; i < end; ++i) { |
477 | 0 | if (IsEmpty(ctrl[i])) { |
478 | 0 | return i; |
479 | 0 | } |
480 | 0 | } |
481 | 0 | ABSL_UNREACHABLE(); |
482 | 0 | } |
483 | | |
484 | | // Finds guaranteed to exist full slot starting from the given position. |
485 | | // NOTE: this function is only triggered for rehash(0), when we need to |
486 | | // go back to SOO state, so we keep it simple. |
487 | 0 | size_t FindFirstFullSlot(size_t start, size_t end, const ctrl_t* ctrl) { |
488 | 0 | for (size_t i = start; i < end; ++i) { |
489 | 0 | if (IsFull(ctrl[i])) { |
490 | 0 | return i; |
491 | 0 | } |
492 | 0 | } |
493 | 0 | ABSL_UNREACHABLE(); |
494 | 0 | } |
495 | | |
496 | 13.2M | void PrepareInsertCommon(CommonFields& common) { |
497 | 13.2M | common.increment_size(); |
498 | 13.2M | common.maybe_increment_generation_on_insert(); |
499 | 13.2M | } |
500 | | |
501 | | // Sets sanitizer poisoning for slot corresponding to control byte being set. |
502 | | inline void DoSanitizeOnSetCtrl(const CommonFields& c, size_t i, ctrl_t h, |
503 | 12.7M | size_t slot_size) { |
504 | 12.7M | const size_t cap = c.capacity(); |
505 | 12.7M | ABSL_ASSUME(cap > kMaxSmallCapacity); |
506 | 12.7M | ABSL_SWISSTABLE_ASSERT(i < cap); |
507 | 12.7M | auto* slot_i = static_cast<const char*>(c.slot_array(cap)) + i * slot_size; |
508 | 12.7M | if (IsFull(h)) { |
509 | 12.7M | SanitizerUnpoisonMemoryRegion(slot_i, slot_size); |
510 | 12.7M | } else { |
511 | 0 | SanitizerPoisonMemoryRegion(slot_i, slot_size); |
512 | 0 | } |
513 | 12.7M | } |
514 | | |
515 | | // Sets `ctrl[i]` to `h`. |
516 | | // |
517 | | // Unlike setting it directly, this function will perform bounds checks and |
518 | | // mirror the value to the cloned tail if necessary. |
519 | | inline void SetCtrlNoSanitizeImpl(size_t cap, ctrl_t* ctrl, size_t i, |
520 | 12.4M | ctrl_t h) { |
521 | 12.4M | ABSL_SWISSTABLE_ASSERT(i < cap); |
522 | | // Select the base pointer rather than the index so `ctrl + cap + 1` does not |
523 | | // depend on `i`. When inlined, it can reuse `ctrl + cap` from `slot_array` |
524 | | // and be hoisted out of loops. |
525 | 12.4M | ctrl_t* cloned_ctrl_base = i < NumClonedBytes() ? ctrl + cap + 1 : ctrl; |
526 | 12.4M | ctrl[i] = h; |
527 | 12.4M | cloned_ctrl_base[i] = h; |
528 | 12.4M | } |
529 | | |
530 | | inline void SetCtrl(const CommonFields& c, size_t cap, ctrl_t* ctrl, size_t i, |
531 | 12.2M | ctrl_t h, size_t slot_size) { |
532 | 12.2M | ABSL_SWISSTABLE_ASSERT(!c.is_small()); |
533 | 12.2M | DoSanitizeOnSetCtrl(c, i, h, slot_size); |
534 | 12.2M | SetCtrlNoSanitizeImpl(cap, ctrl, i, h); |
535 | 12.2M | } |
536 | | |
537 | | inline void SetCtrl(const CommonFields& c, size_t cap, ctrl_t* ctrl, size_t i, |
538 | 12.2M | h2_t h, size_t slot_size) { |
539 | 12.2M | SetCtrl(c, cap, ctrl, i, static_cast<ctrl_t>(h), slot_size); |
540 | 12.2M | } |
541 | | |
542 | | // Sets `ctrl[i]` to `ctrl_t::kSentinel`. |
543 | | // |
544 | | // Unlike setting it directly, this function will perform bounds checks and |
545 | | // mirror the value to the cloned tail if necessary. |
546 | 0 | inline void BlockCtrl(size_t cap, ctrl_t* ctrl, size_t i) { |
547 | 0 | ABSL_SWISSTABLE_ASSERT(!IsSmallCapacity(cap)); |
548 | 0 | SetCtrlNoSanitizeImpl(cap, ctrl, i, ctrl_t::kSentinel); |
549 | 0 | } |
550 | | |
551 | | // Like SetCtrl, but in a single group table, we can save some operations when |
552 | | // setting the cloned control byte. |
553 | | inline void SetCtrlInSingleGroupTable(const CommonFields& c, size_t cap, |
554 | | ctrl_t* ctrl, size_t i, ctrl_t h, |
555 | 474k | size_t slot_size) { |
556 | 474k | ABSL_SWISSTABLE_ASSERT(!c.is_small()); |
557 | 474k | ABSL_SWISSTABLE_ASSERT(cap == c.capacity()); |
558 | 474k | ABSL_SWISSTABLE_ASSERT(ctrl == c.control()); |
559 | 474k | ABSL_SWISSTABLE_ASSERT(is_single_group(cap)); |
560 | 474k | DoSanitizeOnSetCtrl(c, i, h, slot_size); |
561 | 474k | ctrl[i] = h; |
562 | 474k | ctrl[i + cap + 1] = h; |
563 | 474k | } |
564 | | // Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`. |
565 | | inline void SetCtrlInSingleGroupTable(const CommonFields& c, size_t cap, |
566 | | ctrl_t* ctrl, size_t i, h2_t h, |
567 | 474k | size_t slot_size) { |
568 | 474k | SetCtrlInSingleGroupTable(c, cap, ctrl, i, static_cast<ctrl_t>(h), slot_size); |
569 | 474k | } |
570 | | |
571 | | void BlockControlBytes(size_t capacity, ctrl_t* ctrl, |
572 | 0 | size_t blocked_element_count) { |
573 | 0 | ABSL_ASSUME(blocked_element_count <= |
574 | 0 | HashtableInlineData::kMaxBlockedElementCount); |
575 | 0 | while (blocked_element_count > 0) { |
576 | 0 | BlockCtrl(capacity, ctrl, capacity - blocked_element_count); |
577 | 0 | --blocked_element_count; |
578 | 0 | } |
579 | 0 | } |
580 | | |
581 | | void* DropDeletesWithoutResizeAndPrepareInsert( |
582 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
583 | 0 | size_t new_hash, size_t capacity) { |
584 | 0 | void* set = &common; |
585 | 0 | ABSL_SWISSTABLE_ASSERT(common.capacity() == capacity); |
586 | 0 | ABSL_SWISSTABLE_ASSERT(IsValidCapacity(capacity)); |
587 | 0 | ABSL_SWISSTABLE_ASSERT(!is_single_group(capacity)); |
588 | 0 | ABSL_ASSUME(capacity > kMaxSmallCapacity); |
589 | | |
590 | 0 | ctrl_t* ctrl = common.control(); |
591 | 0 | void* slot_array = common.slot_array(capacity); |
592 | | // Algorithm: |
593 | | // - mark all DELETED slots as EMPTY |
594 | | // - mark all FULL slots as DELETED |
595 | | // - for each slot marked as DELETED |
596 | | // hash = Hash(element) |
597 | | // target = find_first_non_full(hash) |
598 | | // if target is in the same group |
599 | | // mark slot as FULL |
600 | | // else if target is EMPTY |
601 | | // transfer element to target |
602 | | // mark slot as EMPTY |
603 | | // mark target as FULL |
604 | | // else if target is DELETED |
605 | | // swap current element with target element |
606 | | // mark target as FULL |
607 | | // repeat procedure for current slot with moved from element (target) |
608 | 0 | const size_t blocked_element_count = common.blocked_element_count(); |
609 | 0 | ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity); |
610 | 0 | BlockControlBytes(capacity, ctrl, blocked_element_count); |
611 | 0 | const void* hash_fn = policy.hash_fn(common); |
612 | 0 | auto hasher = policy.hash_slot; |
613 | 0 | auto transfer_n = policy.transfer_n; |
614 | 0 | const size_t slot_size = policy.slot_size; |
615 | |
|
616 | 0 | size_t total_probe_length = 0; |
617 | 0 | void* slot_ptr = SlotAddress(slot_array, 0, slot_size); |
618 | | |
619 | | // The index of an empty slot that can be used as temporary memory for |
620 | | // the swap operation. |
621 | 0 | constexpr size_t kUnknownId = ~size_t{}; |
622 | 0 | size_t tmp_space_id = kUnknownId; |
623 | |
|
624 | 0 | for (size_t i = 0; i != capacity; |
625 | 0 | ++i, slot_ptr = NextSlot(slot_ptr, slot_size)) { |
626 | 0 | ABSL_SWISSTABLE_ASSERT(slot_ptr == SlotAddress(slot_array, i, slot_size)); |
627 | 0 | if (IsEmpty(ctrl[i])) { |
628 | 0 | tmp_space_id = i; |
629 | 0 | continue; |
630 | 0 | } |
631 | 0 | if (!IsDeleted(ctrl[i])) continue; |
632 | 0 | const size_t hash = (*hasher)(hash_fn, slot_ptr, common.seed().seed()); |
633 | 0 | const FindInfo target = find_first_non_full(capacity, ctrl, hash); |
634 | 0 | const size_t new_i = target.offset; |
635 | 0 | total_probe_length += target.probe_length; |
636 | | |
637 | | // Verify if the old and new i fall within the same group wrt the hash. |
638 | | // If they do, we don't need to move the object as it falls already in the |
639 | | // best probe we can. |
640 | 0 | const size_t probe_offset = probe(ProbeCapacity{capacity}, hash).offset(); |
641 | 0 | const h2_t h2 = H2(hash); |
642 | 0 | const auto probe_index = [probe_offset, capacity](size_t pos) { |
643 | 0 | return ((pos - probe_offset) & capacity) / Group::kWidth; |
644 | 0 | }; |
645 | | |
646 | | // Element doesn't move. |
647 | 0 | if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) { |
648 | 0 | SetCtrl(common, capacity, ctrl, i, h2, slot_size); |
649 | 0 | continue; |
650 | 0 | } |
651 | | |
652 | 0 | void* new_slot_ptr = SlotAddress(slot_array, new_i, slot_size); |
653 | 0 | if (IsEmpty(ctrl[new_i])) { |
654 | | // Transfer element to the empty spot. |
655 | | // SetCtrl poisons/unpoisons the slots so we have to call it at the |
656 | | // right time. |
657 | 0 | SetCtrl(common, capacity, ctrl, new_i, h2, slot_size); |
658 | 0 | (*transfer_n)(set, new_slot_ptr, slot_ptr, 1); |
659 | 0 | SetCtrl(common, capacity, ctrl, i, ctrl_t::kEmpty, slot_size); |
660 | | // Initialize or change empty space id. |
661 | 0 | tmp_space_id = i; |
662 | 0 | } else { |
663 | 0 | ABSL_SWISSTABLE_ASSERT(IsDeleted(ctrl[new_i])); |
664 | 0 | SetCtrl(common, capacity, ctrl, new_i, h2, slot_size); |
665 | | // Until we are done rehashing, DELETED marks previously FULL slots. |
666 | |
|
667 | 0 | if (tmp_space_id == kUnknownId) { |
668 | 0 | tmp_space_id = FindEmptySlot(i + 1, capacity, ctrl); |
669 | 0 | } |
670 | 0 | void* tmp_space = SlotAddress(slot_array, tmp_space_id, slot_size); |
671 | 0 | SanitizerUnpoisonMemoryRegion(tmp_space, slot_size); |
672 | | |
673 | | // Swap i and new_i elements. |
674 | 0 | (*transfer_n)(set, tmp_space, new_slot_ptr, 1); |
675 | 0 | (*transfer_n)(set, new_slot_ptr, slot_ptr, 1); |
676 | 0 | (*transfer_n)(set, slot_ptr, tmp_space, 1); |
677 | |
|
678 | 0 | SanitizerPoisonMemoryRegion(tmp_space, slot_size); |
679 | | |
680 | | // repeat the processing of the ith slot |
681 | 0 | --i; |
682 | 0 | slot_ptr = PrevSlot(slot_ptr, slot_size); |
683 | 0 | } |
684 | 0 | } |
685 | | // Prepare insert for the new element. |
686 | 0 | PrepareInsertCommon(common); |
687 | 0 | ResetGrowthLeft(capacity, common.size() + blocked_element_count, common); |
688 | 0 | FindInfo find_info = find_first_non_full(capacity, ctrl, new_hash); |
689 | 0 | SetCtrl(common, capacity, ctrl, find_info.offset, H2(new_hash), slot_size); |
690 | 0 | common.infoz().RecordInsertMiss(new_hash, find_info.probe_length); |
691 | 0 | common.infoz().RecordRehash(total_probe_length); |
692 | 0 | return SlotAddress(slot_array, find_info.offset, slot_size); |
693 | 0 | } |
694 | | |
695 | 0 | bool WasNeverFull(size_t capacity, ctrl_t* ctrl, size_t index) { |
696 | 0 | if (is_single_group(capacity)) { |
697 | 0 | return true; |
698 | 0 | } |
699 | 0 | const size_t index_before = (index - Group::kWidth) & capacity; |
700 | 0 | const auto empty_after = Group(ctrl + index).MaskEmpty(); |
701 | 0 | const auto empty_before = Group(ctrl + index_before).MaskEmpty(); |
702 | | |
703 | | // We count how many consecutive non empties we have to the right and to the |
704 | | // left of `it`. If the sum is >= kWidth then there is at least one probe |
705 | | // window that might have seen a full group. |
706 | 0 | return empty_before && empty_after && |
707 | 0 | static_cast<size_t>(empty_after.TrailingZeros()) + |
708 | 0 | empty_before.LeadingZeros() < |
709 | 0 | Group::kWidth; |
710 | 0 | } |
711 | | |
712 | | // Updates the control bytes to indicate a completely empty table such that all |
713 | | // control bytes are kEmpty except for the kSentinel bytes. |
714 | | // If the table has blocked elements, last `blocked_element_count` are set to |
715 | | // kSentinel. |
716 | | void ResetCtrl(size_t capacity, ctrl_t* ctrl, void* slot_array, |
717 | 0 | size_t slot_size, size_t blocked_element_count) { |
718 | 0 | ABSL_SWISSTABLE_ASSERT(IsCapacityValidForBlockedElements(capacity) || |
719 | 0 | blocked_element_count == 0); |
720 | 0 | static constexpr size_t kTwoGroupCapacity = 2 * Group::kWidth - 1; |
721 | 0 | if (ABSL_PREDICT_TRUE(capacity <= kTwoGroupCapacity)) { |
722 | 0 | if (IsSmallCapacity(capacity)) return; |
723 | 0 | std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty), Group::kWidth); |
724 | 0 | std::memset(ctrl + capacity, static_cast<int8_t>(ctrl_t::kEmpty), |
725 | 0 | Group::kWidth); |
726 | 0 | if (capacity == kTwoGroupCapacity) { |
727 | 0 | std::memset(ctrl + Group::kWidth, static_cast<int8_t>(ctrl_t::kEmpty), |
728 | 0 | Group::kWidth); |
729 | 0 | } |
730 | 0 | } else { |
731 | 0 | std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty), |
732 | 0 | capacity + 1 + NumClonedBytes()); |
733 | 0 | } |
734 | 0 | ctrl[capacity] = ctrl_t::kSentinel; |
735 | 0 | SanitizerPoisonMemoryRegion(slot_array, |
736 | 0 | slot_size * (capacity - blocked_element_count)); |
737 | 0 | BlockControlBytes(capacity, ctrl, blocked_element_count); |
738 | 0 | } |
739 | | |
740 | | // Initializes control bytes for growing from capacity 1 to 3. |
741 | | // `orig_h2` is placed in the position `SooSlotIndex()`. |
742 | | // `new_h2` is placed in the position `new_offset`. |
743 | | ABSL_ATTRIBUTE_ALWAYS_INLINE inline void InitializeThreeElementsControlBytes( |
744 | 285k | h2_t orig_h2, h2_t new_h2, size_t new_offset, ctrl_t* new_ctrl) { |
745 | 285k | static constexpr size_t kNewCapacity = NextCapacity(SooCapacity()); |
746 | 285k | static_assert(kNewCapacity == 3); |
747 | 285k | static_assert(is_single_group(kNewCapacity)); |
748 | 285k | static_assert(SooSlotIndex() == 1); |
749 | 285k | ABSL_SWISSTABLE_ASSERT(new_offset == 0 || new_offset == 2); |
750 | | |
751 | 285k | static constexpr uint64_t kEmptyXorSentinel = |
752 | 285k | static_cast<uint8_t>(ctrl_t::kEmpty) ^ |
753 | 285k | static_cast<uint8_t>(ctrl_t::kSentinel); |
754 | 285k | static constexpr uint64_t kEmpty64 = static_cast<uint8_t>(ctrl_t::kEmpty); |
755 | 285k | static constexpr size_t kMirroredSooSlotIndex = |
756 | 285k | SooSlotIndex() + kNewCapacity + 1; |
757 | | // The first 8 bytes, where SOO slot original and mirrored positions are |
758 | | // replaced with 0. |
759 | | // Result will look like: E0ESE0EE |
760 | 285k | static constexpr uint64_t kFirstCtrlBytesWithZeroes = |
761 | 285k | k8EmptyBytes ^ (kEmpty64 << (8 * SooSlotIndex())) ^ |
762 | 285k | (kEmptyXorSentinel << (8 * kNewCapacity)) ^ |
763 | 285k | (kEmpty64 << (8 * kMirroredSooSlotIndex)); |
764 | | |
765 | 285k | const uint64_t soo_h2 = static_cast<uint64_t>(orig_h2); |
766 | 285k | const uint64_t new_h2_xor_empty = |
767 | 285k | static_cast<uint64_t>(new_h2 ^ static_cast<uint8_t>(ctrl_t::kEmpty)); |
768 | | // Fill the original and mirrored bytes for SOO slot. |
769 | | // Result will look like: |
770 | | // EHESEHEE |
771 | | // Where H = soo_h2, E = kEmpty, S = kSentinel. |
772 | 285k | uint64_t first_ctrl_bytes = |
773 | 285k | ((soo_h2 << (8 * SooSlotIndex())) | kFirstCtrlBytesWithZeroes) | |
774 | 285k | (soo_h2 << (8 * kMirroredSooSlotIndex)); |
775 | | // Replace original and mirrored empty bytes for the new position. |
776 | | // Result for new_offset 0 will look like: |
777 | | // NHESNHEE |
778 | | // Where H = soo_h2, N = H2(new_hash), E = kEmpty, S = kSentinel. |
779 | | // Result for new_offset 2 will look like: |
780 | | // EHNSEHNE |
781 | 285k | first_ctrl_bytes ^= (new_h2_xor_empty << (8 * new_offset)); |
782 | 285k | size_t new_mirrored_offset = new_offset + kNewCapacity + 1; |
783 | 285k | first_ctrl_bytes ^= (new_h2_xor_empty << (8 * new_mirrored_offset)); |
784 | | |
785 | | // Fill last bytes with kEmpty. |
786 | 285k | std::memset(new_ctrl + kNewCapacity, static_cast<int8_t>(ctrl_t::kEmpty), |
787 | 285k | Group::kWidth); |
788 | | // Overwrite the first 8 bytes with first_ctrl_bytes. |
789 | 285k | absl::little_endian::Store64(new_ctrl, first_ctrl_bytes); |
790 | | |
791 | | // Example for group size 16: |
792 | | // new_ctrl after 1st memset = ???EEEEEEEEEEEEEEEE |
793 | | // new_offset 0: |
794 | | // new_ctrl after 2nd store = NHESNHEEEEEEEEEEEEE |
795 | | // new_offset 2: |
796 | | // new_ctrl after 2nd store = EHNSEHNEEEEEEEEEEEE |
797 | | |
798 | | // Example for group size 8: |
799 | | // new_ctrl after 1st memset = ???EEEEEEEE |
800 | | // new_offset 0: |
801 | | // new_ctrl after 2nd store = NHESNHEEEEE |
802 | | // new_offset 2: |
803 | | // new_ctrl after 2nd store = EHNSEHNEEEE |
804 | 285k | } |
805 | | |
806 | | // Clears the backing array and sets the common fields to the default values for |
807 | | // empty non-allocated tables. REQUIRES: c.capacity > policy.soo_capacity. |
808 | | void ClearBackingArrayNoReuse(CommonFields& c, |
809 | | const PolicyFunctions& __restrict policy, |
810 | 268k | void* alloc) { |
811 | 268k | ABSL_SWISSTABLE_ASSERT(c.capacity() > policy.soo_capacity()); |
812 | | // We need to record infoz before calling dealloc, which will unregister |
813 | | // infoz. |
814 | 268k | c.infoz().RecordClearedReservation(); |
815 | 268k | c.infoz().RecordStorageChanged(0, policy.soo_capacity()); |
816 | 268k | c.infoz().Unregister(); |
817 | 268k | DeallocBackingArrayImpl(c, policy, alloc); |
818 | 268k | c = policy.soo_enabled ? CommonFields{soo_tag_t{}} |
819 | 268k | : CommonFields{non_soo_tag_t{}}; |
820 | 268k | } |
821 | | |
822 | | template <bool kSooEnabled> |
823 | 220k | void* SingleSlotAddress(CommonFields& c) { |
824 | 220k | return kSooEnabled ? c.soo_data() : c.single_non_soo_slot(); |
825 | 220k | } raw_hash_set.cc:void* absl::container_internal::(anonymous namespace)::SingleSlotAddress<false>(absl::container_internal::CommonFields&) Line | Count | Source | 823 | 220k | void* SingleSlotAddress(CommonFields& c) { | 824 | 220k | return kSooEnabled ? c.soo_data() : c.single_non_soo_slot(); | 825 | 220k | } |
Unexecuted instantiation: raw_hash_set.cc:void* absl::container_internal::(anonymous namespace)::SingleSlotAddress<true>(absl::container_internal::CommonFields&) |
826 | | |
827 | | template <bool kSooEnabled> |
828 | 234k | void DecrementSmallSize(CommonFields& c) { |
829 | 234k | if constexpr (kSooEnabled) { |
830 | 14.0k | c.set_empty_soo(); |
831 | 220k | } else { |
832 | 220k | c.decrement_size(); |
833 | 220k | } |
834 | 234k | } raw_hash_set.cc:void absl::container_internal::(anonymous namespace)::DecrementSmallSize<true>(absl::container_internal::CommonFields&) Line | Count | Source | 828 | 14.0k | void DecrementSmallSize(CommonFields& c) { | 829 | 14.0k | if constexpr (kSooEnabled) { | 830 | 14.0k | c.set_empty_soo(); | 831 | | } else { | 832 | | c.decrement_size(); | 833 | | } | 834 | 14.0k | } |
raw_hash_set.cc:void absl::container_internal::(anonymous namespace)::DecrementSmallSize<false>(absl::container_internal::CommonFields&) Line | Count | Source | 828 | 220k | void DecrementSmallSize(CommonFields& c) { | 829 | | if constexpr (kSooEnabled) { | 830 | | c.set_empty_soo(); | 831 | 220k | } else { | 832 | 220k | c.decrement_size(); | 833 | 220k | } | 834 | 220k | } |
|
835 | | |
836 | | void DestructSoo(CommonFields& c, const DtorPolicy& __restrict policy, |
837 | 1.32k | DeallocBackingArrayFn dealloc, void* alloc) { |
838 | 1.32k | ABSL_SWISSTABLE_ASSERT(!c.is_small() || !c.empty()); |
839 | 1.32k | if (c.is_small()) { |
840 | 0 | ABSL_SWISSTABLE_ASSERT(policy.destroy_slot != nullptr); |
841 | 0 | policy.destroy_slot(&c, c.soo_data()); |
842 | 0 | return; |
843 | 0 | } |
844 | 1.32k | if (policy.destroy_slot != nullptr) { |
845 | 0 | DestroySlots(c, policy.slot_size, policy.destroy_slot); |
846 | 0 | } |
847 | 1.32k | UnregisterAndDeallocBackingArray(c, policy, dealloc, alloc); |
848 | 1.32k | } |
849 | | |
850 | | void DestructNonSoo(CommonFields& c, const DtorPolicy& __restrict policy, |
851 | 18.7k | DeallocBackingArrayFn dealloc, void* alloc) { |
852 | 18.7k | ABSL_SWISSTABLE_ASSERT(c.capacity() > 0); |
853 | 18.7k | if (policy.destroy_slot != nullptr) { |
854 | 0 | if (c.is_small()) { |
855 | 0 | if (!c.empty_when_small()) { |
856 | 0 | static_assert(kMaxSmallCapacity == 1); |
857 | 0 | policy.destroy_slot(&c, c.single_non_soo_slot()); |
858 | 0 | } |
859 | 0 | } else { |
860 | 0 | DestroySlots(c, policy.slot_size, policy.destroy_slot); |
861 | 0 | } |
862 | 0 | } |
863 | 18.7k | UnregisterAndDeallocBackingArray(c, policy, dealloc, alloc); |
864 | 18.7k | } |
865 | | |
866 | | } // namespace |
867 | | |
868 | 0 | void EraseMetaOnlySmall(CommonFields& c, bool soo_enabled, size_t slot_size) { |
869 | 0 | ABSL_SWISSTABLE_ASSERT(c.is_small()); |
870 | 0 | if (soo_enabled) { |
871 | 0 | c.set_empty_soo(); |
872 | 0 | return; |
873 | 0 | } |
874 | 0 | c.decrement_size(); |
875 | 0 | c.infoz().RecordErase(); |
876 | 0 | SanitizerPoisonMemoryRegion(SingleSlotAddress</*kSooEnabled=*/false>(c), |
877 | 0 | slot_size); |
878 | 0 | } |
879 | | |
880 | 0 | void EraseMetaOnlyLarge(CommonFields& c, size_t index, size_t slot_size) { |
881 | 0 | ABSL_SWISSTABLE_ASSERT(!c.is_small()); |
882 | 0 | ABSL_SWISSTABLE_ASSERT(IsFull(c.control()[index]) && |
883 | 0 | "erasing a dangling iterator"); |
884 | 0 | c.decrement_size(); |
885 | 0 | c.infoz().RecordErase(); |
886 | |
|
887 | 0 | const size_t cap = c.capacity(); |
888 | 0 | ctrl_t* ctrl = c.control(); |
889 | 0 | if (WasNeverFull(cap, ctrl, index)) { |
890 | 0 | SetCtrl(c, cap, ctrl, index, ctrl_t::kEmpty, slot_size); |
891 | 0 | c.OverwriteFullAsEmpty(); |
892 | 0 | return; |
893 | 0 | } |
894 | | |
895 | 0 | c.OverwriteFullAsDeleted(); |
896 | 0 | SetCtrl(c, cap, ctrl, index, ctrl_t::kDeleted, slot_size); |
897 | 0 | } |
898 | | |
899 | | void ClearBackingArray(CommonFields& c, |
900 | | const PolicyFunctions& __restrict policy, void* alloc, |
901 | 0 | bool reuse) { |
902 | 0 | ABSL_SWISSTABLE_ASSERT(c.capacity() > kMaxSmallCapacity); |
903 | 0 | if (reuse) { |
904 | 0 | const size_t blocked_element_count = c.blocked_element_count(); |
905 | 0 | c.set_size_to_zero(); |
906 | 0 | size_t cap = c.capacity(); |
907 | 0 | ctrl_t* ctrl = c.control(); |
908 | 0 | void* slot_array = c.slot_array(cap); |
909 | 0 | ABSL_SWISSTABLE_ASSERT(cap > policy.soo_capacity()); |
910 | 0 | ResetCtrl(cap, ctrl, slot_array, policy.slot_size, blocked_element_count); |
911 | 0 | ResetGrowthLeft(cap, blocked_element_count, c); |
912 | 0 | ABSL_SWISSTABLE_ASSERT(c.blocked_element_count() == blocked_element_count); |
913 | 0 | c.infoz().RecordStorageChanged(0, c.capacity()); |
914 | 0 | } else { |
915 | 0 | ClearBackingArrayNoReuse(c, policy, alloc); |
916 | 0 | } |
917 | 0 | } |
918 | | |
919 | | void DestroySlots(CommonFields& c, size_t slot_size, |
920 | 0 | DestroySlotFn destroy_slot) { |
921 | 0 | ABSL_SWISSTABLE_ASSERT(!c.is_small()); |
922 | 0 | ABSL_SWISSTABLE_ASSERT(destroy_slot != nullptr); |
923 | 0 | auto destroy_slot_wrapper = [&](const ctrl_t*, void* slot) { |
924 | 0 | destroy_slot(&c, slot); |
925 | 0 | }; |
926 | 0 | if constexpr (SwisstableGenerationsOrDebugEnabled()) { |
927 | 0 | CommonFields common_copy(non_soo_tag_t{}, c); |
928 | 0 | c.set_capacity(HashtableCapacity::CreateDestroyed()); |
929 | 0 | IterateOverFullSlotsImpl(common_copy, slot_size, destroy_slot_wrapper); |
930 | 0 | c.set_capacity(common_copy.capacity()); |
931 | | } else { |
932 | | IterateOverFullSlotsImpl(c, slot_size, destroy_slot_wrapper); |
933 | | } |
934 | 0 | } |
935 | | |
936 | | template <bool kSooEnabled> |
937 | | void Clear(CommonFields& c, const PolicyFunctions& __restrict policy, |
938 | 697k | DestroySlotFn destroy_slot, void* alloc) { |
939 | 697k | if (SwisstableGenerationsEnabled() && |
940 | 0 | c.maybe_invalid_capacity().IsMovedFrom()) { |
941 | 0 | c.set_capacity(policy.soo_capacity()); |
942 | 0 | } |
943 | 697k | c.AssertNotDebugCapacity(); |
944 | 697k | const size_t cap = c.capacity(); |
945 | 697k | if constexpr (kSooEnabled) { |
946 | 40.9k | ABSL_ASSUME(cap > 0); |
947 | 40.9k | } |
948 | 697k | if (c.is_small()) { |
949 | | // In the small case, we keep the backing array allocation because the |
950 | | // memory overhead is small compared to the potential benefits of reuse. |
951 | 428k | if (!c.empty_when_small()) { |
952 | 234k | if (destroy_slot != nullptr) { |
953 | 0 | destroy_slot(&c, SingleSlotAddress<kSooEnabled>(c)); |
954 | 0 | } |
955 | 234k | DecrementSmallSize<kSooEnabled>(c); |
956 | 234k | c.infoz().RecordStorageChanged(0, cap); |
957 | 234k | } |
958 | 428k | } else { |
959 | 268k | if (destroy_slot != nullptr) { |
960 | 0 | DestroySlots(c, policy.slot_size, destroy_slot); |
961 | 0 | } |
962 | | // Iterating over this container is O(bucket_count()). When bucket_count() |
963 | | // is much greater than size(), iteration becomes prohibitively expensive. |
964 | 268k | ClearBackingArrayNoReuse(c, policy, alloc); |
965 | 268k | } |
966 | 697k | c.set_reserved_growth(0); |
967 | 697k | c.set_reservation_size(0); |
968 | 697k | } void absl::container_internal::Clear<true>(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, void (*)(void*, void*), void*) Line | Count | Source | 938 | 40.9k | DestroySlotFn destroy_slot, void* alloc) { | 939 | 40.9k | if (SwisstableGenerationsEnabled() && | 940 | 0 | c.maybe_invalid_capacity().IsMovedFrom()) { | 941 | 0 | c.set_capacity(policy.soo_capacity()); | 942 | 0 | } | 943 | 40.9k | c.AssertNotDebugCapacity(); | 944 | 40.9k | const size_t cap = c.capacity(); | 945 | 40.9k | if constexpr (kSooEnabled) { | 946 | 40.9k | ABSL_ASSUME(cap > 0); | 947 | 40.9k | } | 948 | 40.9k | if (c.is_small()) { | 949 | | // In the small case, we keep the backing array allocation because the | 950 | | // memory overhead is small compared to the potential benefits of reuse. | 951 | 14.6k | if (!c.empty_when_small()) { | 952 | 14.0k | if (destroy_slot != nullptr) { | 953 | 0 | destroy_slot(&c, SingleSlotAddress<kSooEnabled>(c)); | 954 | 0 | } | 955 | 14.0k | DecrementSmallSize<kSooEnabled>(c); | 956 | 14.0k | c.infoz().RecordStorageChanged(0, cap); | 957 | 14.0k | } | 958 | 26.2k | } else { | 959 | 26.2k | if (destroy_slot != nullptr) { | 960 | 0 | DestroySlots(c, policy.slot_size, destroy_slot); | 961 | 0 | } | 962 | | // Iterating over this container is O(bucket_count()). When bucket_count() | 963 | | // is much greater than size(), iteration becomes prohibitively expensive. | 964 | 26.2k | ClearBackingArrayNoReuse(c, policy, alloc); | 965 | 26.2k | } | 966 | 40.9k | c.set_reserved_growth(0); | 967 | 40.9k | c.set_reservation_size(0); | 968 | 40.9k | } |
void absl::container_internal::Clear<false>(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, void (*)(void*, void*), void*) Line | Count | Source | 938 | 656k | DestroySlotFn destroy_slot, void* alloc) { | 939 | 656k | if (SwisstableGenerationsEnabled() && | 940 | 0 | c.maybe_invalid_capacity().IsMovedFrom()) { | 941 | 0 | c.set_capacity(policy.soo_capacity()); | 942 | 0 | } | 943 | 656k | c.AssertNotDebugCapacity(); | 944 | 656k | const size_t cap = c.capacity(); | 945 | | if constexpr (kSooEnabled) { | 946 | | ABSL_ASSUME(cap > 0); | 947 | | } | 948 | 656k | if (c.is_small()) { | 949 | | // In the small case, we keep the backing array allocation because the | 950 | | // memory overhead is small compared to the potential benefits of reuse. | 951 | 413k | if (!c.empty_when_small()) { | 952 | 220k | if (destroy_slot != nullptr) { | 953 | 0 | destroy_slot(&c, SingleSlotAddress<kSooEnabled>(c)); | 954 | 0 | } | 955 | 220k | DecrementSmallSize<kSooEnabled>(c); | 956 | 220k | c.infoz().RecordStorageChanged(0, cap); | 957 | 220k | } | 958 | 413k | } else { | 959 | 242k | if (destroy_slot != nullptr) { | 960 | 0 | DestroySlots(c, policy.slot_size, destroy_slot); | 961 | 0 | } | 962 | | // Iterating over this container is O(bucket_count()). When bucket_count() | 963 | | // is much greater than size(), iteration becomes prohibitively expensive. | 964 | 242k | ClearBackingArrayNoReuse(c, policy, alloc); | 965 | 242k | } | 966 | 656k | c.set_reserved_growth(0); | 967 | 656k | c.set_reservation_size(0); | 968 | 656k | } |
|
969 | | |
970 | | template <bool kSooEnabled> |
971 | | void Destruct(CommonFields& c, const DtorPolicy& __restrict policy, |
972 | 20.0k | DeallocBackingArrayFn dealloc, void* alloc) { |
973 | 20.0k | if constexpr (kSooEnabled) { |
974 | 1.32k | DestructSoo(c, policy, dealloc, alloc); |
975 | 18.7k | } else { |
976 | 18.7k | DestructNonSoo(c, policy, dealloc, alloc); |
977 | 18.7k | } |
978 | 20.0k | } void absl::container_internal::Destruct<true>(absl::container_internal::CommonFields&, absl::container_internal::DtorPolicy const&, void (*)(void*, void*, unsigned long), void*) Line | Count | Source | 972 | 1.32k | DeallocBackingArrayFn dealloc, void* alloc) { | 973 | 1.32k | if constexpr (kSooEnabled) { | 974 | 1.32k | DestructSoo(c, policy, dealloc, alloc); | 975 | | } else { | 976 | | DestructNonSoo(c, policy, dealloc, alloc); | 977 | | } | 978 | 1.32k | } |
void absl::container_internal::Destruct<false>(absl::container_internal::CommonFields&, absl::container_internal::DtorPolicy const&, void (*)(void*, void*, unsigned long), void*) Line | Count | Source | 972 | 18.7k | DeallocBackingArrayFn dealloc, void* alloc) { | 973 | | if constexpr (kSooEnabled) { | 974 | | DestructSoo(c, policy, dealloc, alloc); | 975 | 18.7k | } else { | 976 | 18.7k | DestructNonSoo(c, policy, dealloc, alloc); | 977 | 18.7k | } | 978 | 18.7k | } |
|
979 | | template <bool kSooEnabled> |
980 | | void Destruct(CommonFields& c, const DtorPolicy& __restrict policy, |
981 | 20.0k | DeallocBackingArrayFn dealloc) { |
982 | 20.0k | Destruct<kSooEnabled>(c, policy, dealloc, /*alloc=*/&c); |
983 | 20.0k | } void absl::container_internal::Destruct<true>(absl::container_internal::CommonFields&, absl::container_internal::DtorPolicy const&, void (*)(void*, void*, unsigned long)) Line | Count | Source | 981 | 1.32k | DeallocBackingArrayFn dealloc) { | 982 | 1.32k | Destruct<kSooEnabled>(c, policy, dealloc, /*alloc=*/&c); | 983 | 1.32k | } |
void absl::container_internal::Destruct<false>(absl::container_internal::CommonFields&, absl::container_internal::DtorPolicy const&, void (*)(void*, void*, unsigned long)) Line | Count | Source | 981 | 18.7k | DeallocBackingArrayFn dealloc) { | 982 | 18.7k | Destruct<kSooEnabled>(c, policy, dealloc, /*alloc=*/&c); | 983 | 18.7k | } |
|
984 | | template <bool kSooEnabled> |
985 | 20.0k | void Destruct(CommonFields& c, const DtorPolicy& __restrict policy) { |
986 | 20.0k | Destruct<kSooEnabled>(c, policy, kStandardDeallocBackingArrayFn); |
987 | 20.0k | } void absl::container_internal::Destruct<true>(absl::container_internal::CommonFields&, absl::container_internal::DtorPolicy const&) Line | Count | Source | 985 | 1.32k | void Destruct(CommonFields& c, const DtorPolicy& __restrict policy) { | 986 | 1.32k | Destruct<kSooEnabled>(c, policy, kStandardDeallocBackingArrayFn); | 987 | 1.32k | } |
void absl::container_internal::Destruct<false>(absl::container_internal::CommonFields&, absl::container_internal::DtorPolicy const&) Line | Count | Source | 985 | 18.7k | void Destruct(CommonFields& c, const DtorPolicy& __restrict policy) { | 986 | 18.7k | Destruct<kSooEnabled>(c, policy, kStandardDeallocBackingArrayFn); | 987 | 18.7k | } |
|
988 | | |
989 | | void AssertHashEqConsistentImpl(CommonFields& common, const void* hash_fn, |
990 | | HashSlotFn hash_slot, size_t slot_size, |
991 | | bool soo_enabled, size_t hash, const void* key, |
992 | 0 | EqualToKeyFn equal) { |
993 | 0 | const size_t seed = common.seed().seed(); |
994 | |
|
995 | 0 | const auto assert_consistent = [&](const ctrl_t*, void* slot) { |
996 | 0 | const bool is_key_equal = equal(&common, key, slot); |
997 | 0 | if (!is_key_equal) return; |
998 | | |
999 | 0 | [[maybe_unused]] const bool is_hash_equal = |
1000 | 0 | hash == hash_slot(hash_fn, slot, seed); |
1001 | 0 | assert((!is_key_equal || is_hash_equal) && |
1002 | 0 | "eq(k1, k2) must imply that hash(k1) == hash(k2). " |
1003 | 0 | "hash/eq functors are inconsistent."); |
1004 | 0 | }; |
1005 | |
|
1006 | 0 | if (common.is_small()) { |
1007 | 0 | assert_consistent( |
1008 | 0 | /*unused*/ nullptr, |
1009 | 0 | soo_enabled ? SingleSlotAddress</*kSooEnabled=*/true>(common) |
1010 | 0 | : SingleSlotAddress</*kSooEnabled=*/false>(common)); |
1011 | 0 | return; |
1012 | 0 | } |
1013 | | // We only do validation for small tables so that it's constant time. |
1014 | 0 | if (common.capacity() > 16) return; |
1015 | 0 | IterateOverFullSlots(common, slot_size, assert_consistent); |
1016 | 0 | } |
1017 | | |
1018 | | namespace { |
1019 | | |
1020 | | // Finds the first non-full slot in the probe sequence of `h1` in the large |
1021 | | // table with control bytes `new_ctrl`, marks it as full with `h2` and transfers |
1022 | | // `src_slot` into it. `new_ctrl` is passed explicitly rather than read from `c` |
1023 | | // so that the function can be used while `c` still stores SOO data in place of |
1024 | | // the control pointer. |
1025 | | // This function is shared by the cold paths that move elements to new |
1026 | | // positions (growth of probed elements, rehash and reserve). |
1027 | | // Returns the probe length. |
1028 | | ABSL_ATTRIBUTE_NOINLINE size_t TransferSlotToNewPosition( |
1029 | | CommonFields& c, const PolicyFunctions& __restrict policy, ctrl_t* new_ctrl, |
1030 | 217k | void* src_slot, size_t h1, h2_t h2) { |
1031 | 217k | const size_t new_capacity = c.capacity(); |
1032 | 217k | ABSL_SWISSTABLE_ASSERT(!IsSmallCapacity(new_capacity)); |
1033 | 217k | const FindInfo target = |
1034 | 217k | find_first_non_full_from_h1(new_capacity, new_ctrl, h1); |
1035 | 217k | ABSL_SWISSTABLE_ASSERT(IsEmptyOrDeleted(new_ctrl[target.offset])); |
1036 | 217k | SetCtrlNoSanitizeImpl(new_capacity, new_ctrl, target.offset, |
1037 | 217k | static_cast<ctrl_t>(h2)); |
1038 | 217k | const size_t slot_size = policy.slot_size; |
1039 | 217k | void* dst_slot = |
1040 | 217k | SlotAddress(new_ctrl + NumControlBytesForLargeTable(new_capacity), |
1041 | 217k | target.offset, slot_size); |
1042 | 217k | SanitizerUnpoisonMemoryRegion(dst_slot, slot_size); |
1043 | 217k | policy.transfer_n(&c, dst_slot, src_slot, 1); |
1044 | 217k | return target.probe_length; |
1045 | 217k | } |
1046 | | |
1047 | | // Iterates over full slots in the old table, finds new positions for them and |
1048 | | // transfers the slots to the new backing array (`new_ctrl`, `new_slots`). |
1049 | | // `common` must already have the new capacity and the new seed. `new_ctrl` and |
1050 | | // `new_slots` are passed explicitly rather than read from `common` so that the |
1051 | | // old slot may still be stored inline in `common` (full SOO table). |
1052 | | // This function is used for reserving or rehashing non-empty tables. |
1053 | | // This use case is rare so the function is type erased. |
1054 | | // Returns the total probe length. |
1055 | | size_t FindNewPositionsAndTransferSlots( |
1056 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
1057 | | const ctrl_t* old_ctrl, void* old_slots, size_t old_capacity, |
1058 | 0 | ctrl_t* new_ctrl, void* new_slots) { |
1059 | 0 | const void* hash_fn = policy.hash_fn(common); |
1060 | 0 | const size_t slot_size = policy.slot_size; |
1061 | 0 | const size_t seed = common.seed().seed(); |
1062 | 0 | const size_t new_capacity = common.capacity(); |
1063 | |
|
1064 | 0 | const auto insert_slot = [&](void* slot) -> size_t { |
1065 | 0 | if (IsSmallCapacity(new_capacity)) { |
1066 | 0 | policy.transfer_n(&common, new_slots, slot, 1); |
1067 | 0 | return 0; |
1068 | 0 | } |
1069 | 0 | const size_t hash = policy.hash_slot(hash_fn, slot, seed); |
1070 | 0 | return TransferSlotToNewPosition(common, policy, new_ctrl, slot, H1(hash), |
1071 | 0 | H2(hash)); |
1072 | 0 | }; |
1073 | 0 | if (IsSmallCapacity(old_capacity)) { |
1074 | 0 | if (common.size() == 1) insert_slot(old_slots); |
1075 | 0 | return 0; |
1076 | 0 | } |
1077 | 0 | size_t total_probe_length = 0; |
1078 | 0 | for (size_t i = 0; i < old_capacity; ++i) { |
1079 | 0 | if (IsFull(old_ctrl[i])) { |
1080 | 0 | total_probe_length += insert_slot(old_slots); |
1081 | 0 | } |
1082 | 0 | old_slots = NextSlot(old_slots, slot_size); |
1083 | 0 | } |
1084 | 0 | return total_probe_length; |
1085 | 0 | } |
1086 | | |
1087 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void ReportGrowthToInfozImpl( |
1088 | | CommonFields& common, HashtablezInfoHandle infoz, size_t hash, |
1089 | 0 | size_t total_probe_length, size_t distance_from_desired) { |
1090 | 0 | infoz.RecordStorageChanged(common.size() - 1, common.capacity()); |
1091 | 0 | infoz.RecordRehash(total_probe_length); |
1092 | 0 | infoz.RecordInsertMiss(hash, distance_from_desired); |
1093 | 0 | common.set_has_infoz(); |
1094 | 0 | common.set_infoz(infoz); |
1095 | 0 | } |
1096 | | |
1097 | | // Specialization to avoid passing two 0s from hot function. |
1098 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void |
1099 | | ReportSingleGroupTableGrowthToInfoz(CommonFields& common, ctrl_t* old_ctrl, |
1100 | 0 | size_t hash) { |
1101 | 0 | ReportGrowthToInfozImpl(common, common.infoz_from_control(old_ctrl), hash, |
1102 | 0 | /*total_probe_length=*/0, |
1103 | 0 | /*distance_from_desired=*/0); |
1104 | 0 | } |
1105 | | |
1106 | | // Specialization that computes hash and generates new seed. |
1107 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void |
1108 | | ReportSingleElementTableGrowthToInfozAndForceSample( |
1109 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
1110 | 0 | void* hasher, HashForKeyFn get_hash) { |
1111 | 0 | HashtablezInfoHandle infoz = |
1112 | 0 | ForcedTrySample(policy.slot_size, policy.key_size, policy.value_size, |
1113 | 0 | policy.soo_capacity()); |
1114 | 0 | common.generate_new_seed(/*has_infoz=*/true); |
1115 | 0 | ReportGrowthToInfozImpl(common, infoz, get_hash(hasher, common.seed().seed()), |
1116 | 0 | /*total_probe_length=*/0, |
1117 | 0 | /*distance_from_desired=*/0); |
1118 | 0 | } |
1119 | | |
1120 | | // Outlines cold infoz recording so callers do not inline infoz extraction |
1121 | | // or spill registers across SetCtrl. `common` is first and `hash` is third to |
1122 | | // match argument order in callers. |
1123 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void ReportInsertMissToInfoz( |
1124 | 0 | CommonFields& common, size_t probe_length, size_t hash) { |
1125 | 0 | common.infoz().RecordInsertMiss(hash, probe_length); |
1126 | 0 | } |
1127 | | |
1128 | | // Outlines cold infoz recording with computation of hash so callers can |
1129 | | // avoid calling the hash function and seed extraction in the hot path. |
1130 | | // `common` is first and `get_hash` is third to match argument order in callers. |
1131 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void |
1132 | | ReportInsertMissToInfozAndComputeHash(CommonFields& common, size_t probe_length, |
1133 | 0 | void* hasher, HashForKeyFn get_hash) { |
1134 | 0 | common.infoz().RecordInsertMiss(get_hash(hasher, common.seed().seed()), |
1135 | 0 | probe_length); |
1136 | 0 | } |
1137 | | |
1138 | | // Outlines cold infoz recording with computation of probe_length and slot |
1139 | | // address so callers can skip tracking probe_length and tail-call without |
1140 | | // keeping the slot address live across a call (which would cost a callee-saved |
1141 | | // register and a stack frame on every insert). |
1142 | | // (Returning a value passed in by the caller doesn't work: the compiler infers |
1143 | | // that and keeps the caller's copy alive across the call instead.) |
1144 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void* |
1145 | | ReportInsertMissToInfozAndGetSlot(CommonFields& common, |
1146 | | const PolicyFunctions& __restrict policy, |
1147 | 0 | size_t hash, size_t target) { |
1148 | 0 | const size_t cap = common.capacity(); |
1149 | 0 | auto seq = probe(ProbeCapacity{cap}, hash); |
1150 | 0 | while (((target - seq.offset()) & cap) >= Group::kWidth) { |
1151 | 0 | seq.next(); |
1152 | 0 | } |
1153 | 0 | common.infoz().RecordInsertMiss(hash, seq.index()); |
1154 | 0 | return SlotAddress(common.slot_array(cap), target, policy.slot_size); |
1155 | 0 | } |
1156 | | |
1157 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void ReportGrowthToInfoz( |
1158 | | CommonFields& common, ctrl_t* old_ctrl, size_t hash, |
1159 | 0 | size_t total_probe_length, size_t distance_from_desired) { |
1160 | 0 | ReportGrowthToInfozImpl(common, common.infoz_from_control(old_ctrl), hash, |
1161 | 0 | total_probe_length, distance_from_desired); |
1162 | 0 | } |
1163 | | |
1164 | | // Passed as `total_probe_length` to `ReportResizeToInfoz` when the resize must |
1165 | | // not be reported as a rehash. |
1166 | | constexpr size_t kNoRehash = ~size_t{}; |
1167 | | |
1168 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void ReportResizeToInfoz( |
1169 | | CommonFields& common, HashtablezInfoHandle infoz, |
1170 | 0 | size_t total_probe_length) { |
1171 | 0 | infoz.RecordStorageChanged(common.size(), common.capacity()); |
1172 | 0 | if (total_probe_length != kNoRehash) { |
1173 | 0 | infoz.RecordRehash(total_probe_length); |
1174 | 0 | } |
1175 | 0 | common.set_has_infoz(); |
1176 | 0 | common.set_infoz(infoz); |
1177 | 0 | } |
1178 | | |
1179 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void |
1180 | | ReportResizeToInfozAndForceSample(CommonFields& common, |
1181 | | const PolicyFunctions& __restrict policy, |
1182 | 0 | size_t total_probe_length) { |
1183 | 0 | HashtablezInfoHandle infoz = |
1184 | 0 | ForcedTrySample(policy.slot_size, policy.key_size, policy.value_size, |
1185 | 0 | policy.soo_capacity()); |
1186 | 0 | infoz.RecordStorageChanged(common.size(), common.capacity()); |
1187 | 0 | infoz.RecordRehash(total_probe_length); |
1188 | 0 | common.set_has_infoz(); |
1189 | 0 | common.set_infoz(infoz); |
1190 | 0 | } |
1191 | | |
1192 | | struct BackingArrayPtrs { |
1193 | | ctrl_t* ctrl; |
1194 | | void* slots; |
1195 | | }; |
1196 | | |
1197 | | BackingArrayPtrs AllocBackingArray(CommonFields& common, |
1198 | | const PolicyFunctions& __restrict policy, |
1199 | | const RawHashSetLayout& layout, |
1200 | 1.40M | void* alloc) { |
1201 | | // Perform a direct call in the common case to allow for profile-guided |
1202 | | // heap optimization (PGHO) to understand which allocation function is used. |
1203 | 1.40M | constexpr size_t kDefaultAlignment = BackingArrayAlignment(alignof(size_t)); |
1204 | 1.40M | char* mem = static_cast<char*>( |
1205 | 1.40M | ABSL_PREDICT_TRUE( |
1206 | 1.40M | policy.alloc == |
1207 | 1.40M | (&AllocateBackingArray<kDefaultAlignment, std::allocator<char>>)) |
1208 | 1.40M | ? AllocateBackingArray<kDefaultAlignment, std::allocator<char>>( |
1209 | 1.40M | alloc, layout.alloc_size()) |
1210 | 1.40M | : policy.alloc(alloc, layout.alloc_size())); |
1211 | 1.40M | const GenerationType old_generation = common.generation(); |
1212 | 1.40M | common.set_generation_ptr( |
1213 | 1.40M | reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); |
1214 | 1.40M | common.set_generation(NextGeneration(old_generation)); |
1215 | | |
1216 | 1.40M | return {reinterpret_cast<ctrl_t*>(mem + layout.control_offset()), |
1217 | 1.40M | mem + layout.slot_offset()}; |
1218 | 1.40M | } |
1219 | | |
1220 | | BackingArrayPtrs AllocBackingArray(CommonFields& common, |
1221 | | const PolicyFunctions& __restrict policy, |
1222 | | size_t new_capacity, bool has_infoz, |
1223 | 883k | void* alloc, size_t blocked_element_count) { |
1224 | 883k | return AllocBackingArray( |
1225 | 883k | common, policy, |
1226 | 883k | RawHashSetLayout(new_capacity, policy.slot_size, policy.slot_align_mask, |
1227 | 883k | has_infoz, blocked_element_count), |
1228 | 883k | alloc); |
1229 | 883k | } |
1230 | | |
1231 | | void ResizeEmptyNonAllocatedTableImpl(CommonFields& common, |
1232 | | const PolicyFunctions& __restrict policy, |
1233 | | size_t new_capacity, |
1234 | | size_t blocked_element_count, |
1235 | 0 | bool force_infoz) { |
1236 | 0 | ABSL_SWISSTABLE_ASSERT(IsValidCapacity(new_capacity)); |
1237 | 0 | ABSL_SWISSTABLE_ASSERT(new_capacity > policy.soo_capacity()); |
1238 | 0 | ABSL_SWISSTABLE_ASSERT(!force_infoz || policy.soo_enabled); |
1239 | 0 | ABSL_SWISSTABLE_ASSERT(common.capacity() == policy.soo_capacity()); |
1240 | 0 | ABSL_SWISSTABLE_ASSERT(common.empty()); |
1241 | 0 | const size_t slot_size = policy.slot_size; |
1242 | 0 | const bool has_infoz = |
1243 | 0 | policy.is_hashtablez_enabled && (force_infoz || ShouldSampleNextTable()); |
1244 | |
|
1245 | 0 | common.set_capacity(new_capacity); |
1246 | 0 | common.init_blocked_element_count(blocked_element_count); |
1247 | 0 | const auto [new_ctrl, new_slots] = |
1248 | 0 | AllocBackingArray(common, policy, new_capacity, has_infoz, |
1249 | 0 | policy.get_char_alloc(common), blocked_element_count); |
1250 | 0 | common.set_control(new_ctrl); |
1251 | 0 | common.generate_new_seed(has_infoz); |
1252 | |
|
1253 | 0 | ResetCtrl(new_capacity, new_ctrl, new_slots, slot_size, |
1254 | 0 | blocked_element_count); |
1255 | 0 | ResetGrowthLeft(new_capacity, blocked_element_count, common); |
1256 | |
|
1257 | 0 | if (ABSL_PREDICT_FALSE(has_infoz)) { |
1258 | 0 | ReportResizeToInfozAndForceSample(common, policy, 0); |
1259 | 0 | } |
1260 | 0 | } |
1261 | | |
1262 | | void AssertSoo([[maybe_unused]] CommonFields& common, |
1263 | 27.5k | [[maybe_unused]] const PolicyFunctions& __restrict policy) { |
1264 | 27.5k | ABSL_SWISSTABLE_ASSERT(policy.soo_enabled); |
1265 | 27.5k | ABSL_SWISSTABLE_ASSERT(common.capacity() == policy.soo_capacity()); |
1266 | 27.5k | } |
1267 | | void AssertFullSoo([[maybe_unused]] CommonFields& common, |
1268 | 0 | [[maybe_unused]] const PolicyFunctions& __restrict policy) { |
1269 | 0 | AssertSoo(common, policy); |
1270 | 0 | ABSL_SWISSTABLE_ASSERT(common.size() == policy.soo_capacity()); |
1271 | 0 | } |
1272 | | |
1273 | | // Resizes an allocated table or a full SOO table to `new_capacity`, changes the |
1274 | | // table seed and transfers all elements to the new backing array. |
1275 | | // Allocated tables keep their infoz. If `force_sample_soo`, the table must be a |
1276 | | // full SOO table and it is forced to be sampled. If the table was still not |
1277 | | // sampled, it is not resized. |
1278 | | // It is rare to resize a table that is not empty or to resize an SOO table to |
1279 | | // a large size, so this function is shared by all such cases and is kept out of |
1280 | | // line. |
1281 | | // Requires: |
1282 | | // 1. `new_capacity > policy.soo_capacity()`. |
1283 | | // 2. `common.capacity() > policy.soo_capacity()` OR `!common.empty()`. |
1284 | | ABSL_ATTRIBUTE_NOINLINE void ResizeTableWithSeedChangeImpl( |
1285 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
1286 | 0 | size_t new_capacity, bool force_sample_soo) { |
1287 | 0 | ABSL_SWISSTABLE_ASSERT(IsValidCapacity(new_capacity)); |
1288 | 0 | ABSL_SWISSTABLE_ASSERT(new_capacity > policy.soo_capacity()); |
1289 | 0 | const size_t old_capacity = common.capacity(); |
1290 | 0 | const bool was_soo = old_capacity == policy.soo_capacity(); |
1291 | 0 | ABSL_SWISSTABLE_ASSERT(!was_soo || common.size() == policy.soo_capacity()); |
1292 | 0 | ABSL_SWISSTABLE_ASSERT(!force_sample_soo || was_soo); |
1293 | | // Note: `has_infoz` determines the layout of the backing array and may be set |
1294 | | // even if `infoz` is not sampled (see `ReportResizeToInfozAndForceSample`). |
1295 | 0 | HashtablezInfoHandle infoz; |
1296 | 0 | bool has_infoz; |
1297 | 0 | if (force_sample_soo) { |
1298 | 0 | if (!policy.is_hashtablez_enabled) return; |
1299 | 0 | infoz = ForcedTrySample(policy.slot_size, policy.key_size, |
1300 | 0 | policy.value_size, policy.soo_capacity()); |
1301 | | // If the table was not sampled, do not resize. |
1302 | 0 | if (!infoz.IsSampled()) return; |
1303 | 0 | has_infoz = true; |
1304 | 0 | } else { |
1305 | 0 | has_infoz = common.has_infoz(); |
1306 | 0 | infoz = common.infoz(); |
1307 | 0 | } |
1308 | | // `old_ctrl` is not used for SOO tables. |
1309 | 0 | ctrl_t* const old_ctrl = was_soo ? nullptr : common.control(); |
1310 | 0 | void* const old_slots = was_soo ? common.soo_data() |
1311 | 0 | : (IsSmallCapacity(old_capacity) |
1312 | 0 | ? common.single_non_soo_slot() |
1313 | 0 | : common.slot_array(old_capacity)); |
1314 | 0 | const size_t old_blocked_element_count = common.blocked_element_count(); |
1315 | 0 | const size_t slot_size = policy.slot_size; |
1316 | |
|
1317 | 0 | common.set_capacity(new_capacity); |
1318 | 0 | common.set_blocked_element_count_to_zero(); |
1319 | 0 | const auto [new_ctrl, new_slots] = AllocBackingArray( |
1320 | 0 | common, policy, new_capacity, has_infoz, policy.get_char_alloc(common), |
1321 | 0 | /*blocked_element_count=*/0); |
1322 | 0 | common.generate_new_seed(has_infoz); |
1323 | 0 | ResetCtrl(new_capacity, new_ctrl, new_slots, slot_size, |
1324 | 0 | /*blocked_element_count=*/0); |
1325 | 0 | const size_t total_probe_length = FindNewPositionsAndTransferSlots( |
1326 | 0 | common, policy, old_ctrl, old_slots, old_capacity, new_ctrl, new_slots); |
1327 | | // Control is set in `common` only after the transfer to avoid overriding the |
1328 | | // SOO data. |
1329 | 0 | common.set_control(new_ctrl); |
1330 | 0 | if (!was_soo) { |
1331 | 0 | DeallocBackingArrayImpl(policy.get_char_alloc(common), old_capacity, |
1332 | 0 | old_ctrl, slot_size, policy.slot_align_mask, |
1333 | 0 | has_infoz, old_blocked_element_count, |
1334 | 0 | policy.dealloc); |
1335 | 0 | } |
1336 | 0 | ResetGrowthLeft(new_capacity, common.size(), common); |
1337 | 0 | if (ABSL_PREDICT_FALSE(has_infoz)) { |
1338 | | // Forced sampling of a full SOO table is not reported as a rehash. |
1339 | 0 | ReportResizeToInfoz(common, infoz, |
1340 | 0 | force_sample_soo ? kNoRehash : total_probe_length); |
1341 | 0 | } |
1342 | 0 | } |
1343 | | |
1344 | | void GrowIntoSingleGroupShuffleControlBytes(ctrl_t* __restrict old_ctrl, |
1345 | | size_t old_capacity, |
1346 | | size_t old_blocked_element_count, |
1347 | | ctrl_t* __restrict new_ctrl, |
1348 | 474k | size_t new_capacity) { |
1349 | 474k | ABSL_SWISSTABLE_ASSERT(is_single_group(new_capacity)); |
1350 | 474k | constexpr size_t kHalfWidth = Group::kWidth / 2; |
1351 | 474k | ABSL_ASSUME(old_capacity < kHalfWidth); |
1352 | 474k | ABSL_ASSUME(old_capacity > 0); |
1353 | 474k | static_assert(Group::kWidth == 8 || Group::kWidth == 16, |
1354 | 474k | "Group size is not supported."); |
1355 | | |
1356 | | // NOTE: operations are done with compile time known size = 8. |
1357 | | // Compiler optimizes that into single ASM operation. |
1358 | | |
1359 | | // Load the bytes from old_capacity. This contains |
1360 | | // - the sentinel byte |
1361 | | // - all the old control bytes |
1362 | | // - the rest is filled with kEmpty bytes |
1363 | | // Example: |
1364 | | // old_ctrl = 012S012EEEEEEEEE... |
1365 | | // copied_bytes = S012EEEE |
1366 | | // Example with blocked elements: |
1367 | | // old_ctrl = 01SS01SEEEEEEEEE... |
1368 | | // copied_bytes = S01SEEEE |
1369 | 474k | uint64_t copied_bytes = absl::little_endian::Load64(old_ctrl + old_capacity); |
1370 | | |
1371 | | // We change the sentinel byte to kEmpty before storing to both the start of |
1372 | | // the new_ctrl, and past the end of the new_ctrl later for the new cloned |
1373 | | // bytes. Note that this is faster than setting the sentinel byte to kEmpty |
1374 | | // after the copy directly in new_ctrl because we are limited on store |
1375 | | // bandwidth. |
1376 | 474k | static constexpr uint64_t kEmptyXorSentinel = |
1377 | 474k | static_cast<uint8_t>(ctrl_t::kEmpty) ^ |
1378 | 474k | static_cast<uint8_t>(ctrl_t::kSentinel); |
1379 | | |
1380 | | // Replace the first byte kSentinel with kEmpty. |
1381 | | // Resulting bytes will be shifted by one byte old control blocks. |
1382 | | // Example: |
1383 | | // old_ctrl = 012S012EEEEEEEEE... |
1384 | | // before = S012EEEE |
1385 | | // after = E012EEEE |
1386 | 474k | copied_bytes ^= kEmptyXorSentinel; |
1387 | | |
1388 | 474k | if (ABSL_PREDICT_FALSE(old_blocked_element_count > 0)) { |
1389 | | // Replacing blocked sentinel elements with kEmpty. |
1390 | 0 | static constexpr uint64_t kAllBytesEmptyXorSentinel = |
1391 | 0 | kEmptyXorSentinel * uint64_t{0x0101010101010101}; |
1392 | 0 | uint64_t blocked_mask = kAllBytesEmptyXorSentinel; |
1393 | | // Keep old_blocked_element_count bytes in the mask. |
1394 | 0 | blocked_mask >>= 64 - old_blocked_element_count * 8; |
1395 | | // Shift the mask to the start of the blocked elements bytes. |
1396 | 0 | blocked_mask <<= (old_capacity - old_blocked_element_count + 1) * 8; |
1397 | | // Example with blocked elements: |
1398 | | // old_ctrl = 0SSS0SSEEEEEEEEE... |
1399 | | // before = E0SSEEEE |
1400 | | // after = E0EEEEEE |
1401 | 0 | copied_bytes ^= blocked_mask; |
1402 | 0 | } |
1403 | | |
1404 | 474k | if (Group::kWidth == 8) { |
1405 | | // With group size 8, we can grow with two write operations. |
1406 | 0 | ABSL_SWISSTABLE_ASSERT(old_capacity < 8 && |
1407 | 0 | "old_capacity is too large for group size 8"); |
1408 | 0 | absl::little_endian::Store64(new_ctrl, copied_bytes); |
1409 | |
|
1410 | 0 | static constexpr uint64_t kSentinal64 = |
1411 | 0 | static_cast<uint8_t>(ctrl_t::kSentinel); |
1412 | | |
1413 | | // Prepend kSentinel byte to the beginning of copied_bytes. |
1414 | | // We have maximum 3 non-empty bytes at the beginning of copied_bytes for |
1415 | | // group size 8. |
1416 | | // Example: |
1417 | | // old_ctrl = 012S012EEEE |
1418 | | // before = E012EEEE |
1419 | | // after = SE012EEE |
1420 | 0 | copied_bytes = (copied_bytes << 8) ^ kSentinal64; |
1421 | 0 | absl::little_endian::Store64(new_ctrl + new_capacity, copied_bytes); |
1422 | | // Example for capacity 3: |
1423 | | // old_ctrl = 012S012EEEE |
1424 | | // After the first store: |
1425 | | // >! |
1426 | | // new_ctrl = E012EEEE??????? |
1427 | | // After the second store: |
1428 | | // >! |
1429 | | // new_ctrl = E012EEESE012EEE |
1430 | 0 | return; |
1431 | 0 | } |
1432 | | |
1433 | 474k | ABSL_SWISSTABLE_ASSERT(Group::kWidth == 16); // NOLINT(misc-static-assert) |
1434 | | |
1435 | | // Fill the second half of the main control bytes with kEmpty. |
1436 | | // For small capacity that may write into mirrored control bytes. |
1437 | | // It is fine as we will overwrite all the bytes later. |
1438 | 474k | std::memset(new_ctrl + kHalfWidth, static_cast<int8_t>(ctrl_t::kEmpty), |
1439 | 474k | kHalfWidth); |
1440 | | // Fill the second half of the mirrored control bytes with kEmpty. |
1441 | 474k | std::memset(new_ctrl + new_capacity + kHalfWidth, |
1442 | 474k | static_cast<int8_t>(ctrl_t::kEmpty), kHalfWidth); |
1443 | | // Copy the first half of the non-mirrored control bytes. |
1444 | 474k | absl::little_endian::Store64(new_ctrl, copied_bytes); |
1445 | 474k | new_ctrl[new_capacity] = ctrl_t::kSentinel; |
1446 | | // Copy the first half of the mirrored control bytes. |
1447 | 474k | absl::little_endian::Store64(new_ctrl + new_capacity + 1, copied_bytes); |
1448 | | |
1449 | | // Example for growth capacity 1->3: |
1450 | | // old_ctrl = 0S0EEEEEEEEEEEEEE |
1451 | | // new_ctrl at the end = E0ESE0EEEEEEEEEEEEE |
1452 | | // >! |
1453 | | // new_ctrl after 1st memset = ????????EEEEEEEE??? |
1454 | | // >! |
1455 | | // new_ctrl after 2nd memset = ????????EEEEEEEEEEE |
1456 | | // >! |
1457 | | // new_ctrl after 1st store = E0EEEEEEEEEEEEEEEEE |
1458 | | // new_ctrl after kSentinel = E0ESEEEEEEEEEEEEEEE |
1459 | | // >! |
1460 | | // new_ctrl after 2nd store = E0ESE0EEEEEEEEEEEEE |
1461 | | |
1462 | | // Example for growth capacity 3->7: |
1463 | | // old_ctrl = 012S012EEEEEEEEEEEE |
1464 | | // new_ctrl at the end = E012EEESE012EEEEEEEEEEE |
1465 | | // >! |
1466 | | // new_ctrl after 1st memset = ????????EEEEEEEE??????? |
1467 | | // >! |
1468 | | // new_ctrl after 2nd memset = ????????EEEEEEEEEEEEEEE |
1469 | | // >! |
1470 | | // new_ctrl after 1st store = E012EEEEEEEEEEEEEEEEEEE |
1471 | | // new_ctrl after kSentinel = E012EEESEEEEEEEEEEEEEEE |
1472 | | // >! |
1473 | | // new_ctrl after 2nd store = E012EEESE012EEEEEEEEEEE |
1474 | | |
1475 | | // Example for growth capacity 7->15: |
1476 | | // old_ctrl = 0123456S0123456EEEEEEEE |
1477 | | // new_ctrl at the end = E0123456EEEEEEESE0123456EEEEEEE |
1478 | | // >! |
1479 | | // new_ctrl after 1st memset = ????????EEEEEEEE??????????????? |
1480 | | // >! |
1481 | | // new_ctrl after 2nd memset = ????????EEEEEEEE???????EEEEEEEE |
1482 | | // >! |
1483 | | // new_ctrl after 1st store = E0123456EEEEEEEE???????EEEEEEEE |
1484 | | // new_ctrl after kSentinel = E0123456EEEEEEES???????EEEEEEEE |
1485 | | // >! |
1486 | | // new_ctrl after 2nd store = E0123456EEEEEEESE0123456EEEEEEE |
1487 | 474k | } |
1488 | | |
1489 | | // Size of the buffer we allocate on stack for storing probed elements in |
1490 | | // GrowToNextCapacity algorithm. |
1491 | | constexpr size_t kProbedElementsBufferSize = 512; |
1492 | | |
1493 | | // Decodes information about probed elements from contiguous memory. |
1494 | | // Finds new position for each element and transfers it to the new slots. |
1495 | | // Returns the total probe length. |
1496 | | template <typename ProbedItem> |
1497 | | ABSL_ATTRIBUTE_NOINLINE size_t DecodeAndInsertImpl( |
1498 | | CommonFields& c, const PolicyFunctions& __restrict policy, |
1499 | 103k | const ProbedItem* start, const ProbedItem* end, void* old_slots) { |
1500 | 103k | ABSL_SWISSTABLE_ASSERT(!c.is_small()); |
1501 | 103k | ctrl_t* new_ctrl = c.control(); |
1502 | 103k | const size_t slot_size = policy.slot_size; |
1503 | 103k | size_t total_probe_length = 0; |
1504 | 321k | for (; start < end; ++start) { |
1505 | 217k | const size_t old_index = static_cast<size_t>(start->source_offset); |
1506 | 217k | ABSL_SWISSTABLE_ASSERT(old_index < c.capacity() / 2); |
1507 | 217k | total_probe_length += TransferSlotToNewPosition( |
1508 | 217k | c, policy, new_ctrl, SlotAddress(old_slots, old_index, slot_size), |
1509 | 217k | static_cast<size_t>(start->h1), static_cast<h2_t>(start->h2)); |
1510 | 217k | } |
1511 | 103k | return total_probe_length; |
1512 | 103k | } raw_hash_set.cc:unsigned long absl::container_internal::(anonymous namespace)::DecodeAndInsertImpl<absl::container_internal::ProbedItemImpl<unsigned int, 32ul> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ProbedItemImpl<unsigned int, 32ul> const*, absl::container_internal::ProbedItemImpl<unsigned int, 32ul> const*, void*) Line | Count | Source | 1499 | 103k | const ProbedItem* start, const ProbedItem* end, void* old_slots) { | 1500 | 103k | ABSL_SWISSTABLE_ASSERT(!c.is_small()); | 1501 | 103k | ctrl_t* new_ctrl = c.control(); | 1502 | 103k | const size_t slot_size = policy.slot_size; | 1503 | 103k | size_t total_probe_length = 0; | 1504 | 321k | for (; start < end; ++start) { | 1505 | 217k | const size_t old_index = static_cast<size_t>(start->source_offset); | 1506 | 217k | ABSL_SWISSTABLE_ASSERT(old_index < c.capacity() / 2); | 1507 | 217k | total_probe_length += TransferSlotToNewPosition( | 1508 | 217k | c, policy, new_ctrl, SlotAddress(old_slots, old_index, slot_size), | 1509 | 217k | static_cast<size_t>(start->h1), static_cast<h2_t>(start->h2)); | 1510 | 217k | } | 1511 | 103k | return total_probe_length; | 1512 | 103k | } |
Unexecuted instantiation: raw_hash_set.cc:unsigned long absl::container_internal::(anonymous namespace)::DecodeAndInsertImpl<absl::container_internal::ProbedItemImpl<unsigned long, 64ul> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ProbedItemImpl<unsigned long, 64ul> const*, absl::container_internal::ProbedItemImpl<unsigned long, 64ul> const*, void*) Unexecuted instantiation: raw_hash_set.cc:unsigned long absl::container_internal::(anonymous namespace)::DecodeAndInsertImpl<absl::container_internal::ProbedItemImpl<unsigned long, 122ul> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ProbedItemImpl<unsigned long, 122ul> const*, absl::container_internal::ProbedItemImpl<unsigned long, 122ul> const*, void*) |
1513 | | |
1514 | | // Sentinel value for the start of marked elements. |
1515 | | // Signals that there are no marked elements. |
1516 | | constexpr size_t kNoMarkedElementsSentinel = ~size_t{}; |
1517 | | |
1518 | | // Process probed elements that did not fit into available buffers. |
1519 | | // We marked them in control bytes as kMarkedForSlowTransfer. |
1520 | | // Hash recomputation and full probing is done here. |
1521 | | // This use case should be extremely rare. |
1522 | | ABSL_ATTRIBUTE_NOINLINE size_t ProcessProbedMarkedElements( |
1523 | | CommonFields& c, const PolicyFunctions& __restrict policy, ctrl_t* old_ctrl, |
1524 | 0 | void* old_slots, size_t start) { |
1525 | 0 | const size_t old_capacity = PreviousCapacity(c.capacity()); |
1526 | 0 | const size_t slot_size = policy.slot_size; |
1527 | 0 | size_t total_probe_length = 0; |
1528 | 0 | const void* hash_fn = policy.hash_fn(c); |
1529 | 0 | auto hash_slot = policy.hash_slot; |
1530 | 0 | const size_t seed = c.seed().seed(); |
1531 | 0 | for (size_t old_index = start; old_index < old_capacity; ++old_index) { |
1532 | 0 | if (old_ctrl[old_index] != ctrl_t::kMarkedForSlowTransfer) { |
1533 | 0 | continue; |
1534 | 0 | } |
1535 | 0 | void* src_slot = SlotAddress(old_slots, old_index, slot_size); |
1536 | 0 | const size_t hash = hash_slot(hash_fn, src_slot, seed); |
1537 | 0 | total_probe_length += TransferSlotToNewPosition( |
1538 | 0 | c, policy, c.control(), src_slot, H1(hash), H2(hash)); |
1539 | 0 | } |
1540 | 0 | return total_probe_length; |
1541 | 0 | } |
1542 | | |
1543 | | // The largest old capacity for which it is guaranteed that all probed elements |
1544 | | // fit in ProbedItemEncoder's local buffer. |
1545 | | // For such tables, `encode_probed_element` is trivial. |
1546 | | constexpr size_t kMaxLocalBufferOldCapacity = |
1547 | | kProbedElementsBufferSize / sizeof(ProbedItem4Bytes) - 1; |
1548 | | static_assert(IsValidCapacity(kMaxLocalBufferOldCapacity)); |
1549 | | constexpr size_t kMaxLocalBufferNewCapacity = |
1550 | | NextCapacity(kMaxLocalBufferOldCapacity); |
1551 | | static_assert(kMaxLocalBufferNewCapacity <= ProbedItem4Bytes::kMaxNewCapacity); |
1552 | | static_assert(NextCapacity(kMaxLocalBufferNewCapacity) <= |
1553 | | ProbedItem4Bytes::kMaxNewCapacity); |
1554 | | |
1555 | | // Initializes mirrored control bytes after |
1556 | | // transfer_unprobed_elements_to_next_capacity. |
1557 | 381k | void InitializeMirroredControlBytes(ctrl_t* new_ctrl, size_t new_capacity) { |
1558 | 381k | std::memcpy(new_ctrl + new_capacity + 1, new_ctrl, Group::kWidth - 1); |
1559 | 381k | new_ctrl[new_capacity] = ctrl_t::kSentinel; |
1560 | 381k | } |
1561 | | |
1562 | | // Encodes probed elements into available memory. |
1563 | | // At first, a local (on stack) buffer is used. The size of the buffer is |
1564 | | // kProbedElementsBufferSize bytes. |
1565 | | // When the local buffer is full, we switch to `control_` buffer. We are allowed |
1566 | | // to overwrite `control_` buffer till the `source_offset` byte. In case we have |
1567 | | // no space in `control_` buffer, we fallback to a naive algorithm for all the |
1568 | | // rest of the probed elements. We mark elements as kSentinel in control bytes |
1569 | | // and later process them fully. See ProcessMarkedElements for details. It |
1570 | | // should be extremely rare. |
1571 | | template <typename ProbedItemType, |
1572 | | // If true, we only use the local buffer and never switch to the |
1573 | | // control buffer. |
1574 | | bool kGuaranteedFitToBuffer = false> |
1575 | | class ProbedItemEncoder { |
1576 | | public: |
1577 | | using ProbedItem = ProbedItemType; |
1578 | | ProbedItemEncoder(CommonFields& common, |
1579 | | const PolicyFunctions& __restrict policy, ctrl_t* control, |
1580 | | void* old_slots, size_t new_capacity) |
1581 | 381k | : common_(common), |
1582 | 381k | policy_(policy), |
1583 | 381k | control_(control), |
1584 | 381k | old_slots_(old_slots), |
1585 | 381k | new_capacity_(new_capacity), |
1586 | 381k | new_ctrl_(common.control()), |
1587 | 381k | new_slots_(common.slot_array(new_capacity)) {}raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true>::ProbedItemEncoder(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long) Line | Count | Source | 1581 | 362k | : common_(common), | 1582 | 362k | policy_(policy), | 1583 | 362k | control_(control), | 1584 | 362k | old_slots_(old_slots), | 1585 | 362k | new_capacity_(new_capacity), | 1586 | 362k | new_ctrl_(common.control()), | 1587 | 362k | new_slots_(common.slot_array(new_capacity)) {} |
raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false>::ProbedItemEncoder(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long) Line | Count | Source | 1581 | 18.7k | : common_(common), | 1582 | 18.7k | policy_(policy), | 1583 | 18.7k | control_(control), | 1584 | 18.7k | old_slots_(old_slots), | 1585 | 18.7k | new_capacity_(new_capacity), | 1586 | 18.7k | new_ctrl_(common.control()), | 1587 | 18.7k | new_slots_(common.slot_array(new_capacity)) {} |
Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false>::ProbedItemEncoder(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long) Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false>::ProbedItemEncoder(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long) |
1588 | | |
1589 | | // Transfers item to the first probe group if possible, otherwise encodes it |
1590 | | // into the best available location. |
1591 | 545k | void EncodeProbedElement(size_t hash, size_t source_offset) { |
1592 | 545k | size_t h1 = H1(hash); |
1593 | 545k | const size_t new_capacity = new_capacity_; |
1594 | 545k | const size_t old_capacity = PreviousCapacity(new_capacity); |
1595 | 545k | ABSL_SWISSTABLE_ASSERT(((source_offset - h1) & old_capacity) >= |
1596 | 545k | Group::kWidth); |
1597 | | // Try to insert element into the first probe group. |
1598 | | // new_ctrl is not yet fully initialized so we can't use regular search via |
1599 | | // find_first_non_full. |
1600 | | // |
1601 | | // We can search in the first probe group only if it is located in already |
1602 | | // initialized part of the table. |
1603 | 545k | if (ABSL_PREDICT_TRUE((h1 & old_capacity) < source_offset)) { |
1604 | 328k | ctrl_t* new_ctrl = new_ctrl_; |
1605 | 328k | size_t offset = h1 & new_capacity; |
1606 | 328k | Group new_g(new_ctrl + offset); |
1607 | 328k | if (auto mask = new_g.MaskNonFull(); ABSL_PREDICT_TRUE(mask)) { |
1608 | 327k | size_t new_index = offset + mask.LowestBitSet(); |
1609 | 327k | ABSL_SWISSTABLE_ASSERT((new_index & old_capacity) <= source_offset); |
1610 | 327k | ABSL_SWISSTABLE_ASSERT(IsEmpty(new_ctrl[new_index])); |
1611 | 327k | new_ctrl[new_index] = static_cast<ctrl_t>(H2(hash)); |
1612 | 327k | const size_t slot_size = policy_.slot_size; |
1613 | 327k | void* new_slot = SlotAddress(new_slots_, new_index, slot_size); |
1614 | 327k | void* old_slot = SlotAddress(old_slots_, source_offset, slot_size); |
1615 | 327k | SanitizerUnpoisonMemoryRegion(new_slot, slot_size); |
1616 | 327k | policy_.transfer_n(&common_, new_slot, old_slot, 1); |
1617 | 327k | SanitizerPoisonMemoryRegion(old_slot, slot_size); |
1618 | 327k | return; |
1619 | 327k | } |
1620 | 328k | } |
1621 | 217k | EncodeItem(ProbedItem(H2(hash), source_offset, h1)); |
1622 | 217k | } raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true>::EncodeProbedElement(unsigned long, unsigned long) Line | Count | Source | 1591 | 339k | void EncodeProbedElement(size_t hash, size_t source_offset) { | 1592 | 339k | size_t h1 = H1(hash); | 1593 | 339k | const size_t new_capacity = new_capacity_; | 1594 | 339k | const size_t old_capacity = PreviousCapacity(new_capacity); | 1595 | 339k | ABSL_SWISSTABLE_ASSERT(((source_offset - h1) & old_capacity) >= | 1596 | 339k | Group::kWidth); | 1597 | | // Try to insert element into the first probe group. | 1598 | | // new_ctrl is not yet fully initialized so we can't use regular search via | 1599 | | // find_first_non_full. | 1600 | | // | 1601 | | // We can search in the first probe group only if it is located in already | 1602 | | // initialized part of the table. | 1603 | 339k | if (ABSL_PREDICT_TRUE((h1 & old_capacity) < source_offset)) { | 1604 | 165k | ctrl_t* new_ctrl = new_ctrl_; | 1605 | 165k | size_t offset = h1 & new_capacity; | 1606 | 165k | Group new_g(new_ctrl + offset); | 1607 | 165k | if (auto mask = new_g.MaskNonFull(); ABSL_PREDICT_TRUE(mask)) { | 1608 | 164k | size_t new_index = offset + mask.LowestBitSet(); | 1609 | 164k | ABSL_SWISSTABLE_ASSERT((new_index & old_capacity) <= source_offset); | 1610 | 164k | ABSL_SWISSTABLE_ASSERT(IsEmpty(new_ctrl[new_index])); | 1611 | 164k | new_ctrl[new_index] = static_cast<ctrl_t>(H2(hash)); | 1612 | 164k | const size_t slot_size = policy_.slot_size; | 1613 | 164k | void* new_slot = SlotAddress(new_slots_, new_index, slot_size); | 1614 | 164k | void* old_slot = SlotAddress(old_slots_, source_offset, slot_size); | 1615 | 164k | SanitizerUnpoisonMemoryRegion(new_slot, slot_size); | 1616 | 164k | policy_.transfer_n(&common_, new_slot, old_slot, 1); | 1617 | 164k | SanitizerPoisonMemoryRegion(old_slot, slot_size); | 1618 | 164k | return; | 1619 | 164k | } | 1620 | 165k | } | 1621 | 175k | EncodeItem(ProbedItem(H2(hash), source_offset, h1)); | 1622 | 175k | } |
raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false>::EncodeProbedElement(unsigned long, unsigned long) Line | Count | Source | 1591 | 205k | void EncodeProbedElement(size_t hash, size_t source_offset) { | 1592 | 205k | size_t h1 = H1(hash); | 1593 | 205k | const size_t new_capacity = new_capacity_; | 1594 | 205k | const size_t old_capacity = PreviousCapacity(new_capacity); | 1595 | 205k | ABSL_SWISSTABLE_ASSERT(((source_offset - h1) & old_capacity) >= | 1596 | 205k | Group::kWidth); | 1597 | | // Try to insert element into the first probe group. | 1598 | | // new_ctrl is not yet fully initialized so we can't use regular search via | 1599 | | // find_first_non_full. | 1600 | | // | 1601 | | // We can search in the first probe group only if it is located in already | 1602 | | // initialized part of the table. | 1603 | 205k | if (ABSL_PREDICT_TRUE((h1 & old_capacity) < source_offset)) { | 1604 | 163k | ctrl_t* new_ctrl = new_ctrl_; | 1605 | 163k | size_t offset = h1 & new_capacity; | 1606 | 163k | Group new_g(new_ctrl + offset); | 1607 | 163k | if (auto mask = new_g.MaskNonFull(); ABSL_PREDICT_TRUE(mask)) { | 1608 | 163k | size_t new_index = offset + mask.LowestBitSet(); | 1609 | 163k | ABSL_SWISSTABLE_ASSERT((new_index & old_capacity) <= source_offset); | 1610 | 163k | ABSL_SWISSTABLE_ASSERT(IsEmpty(new_ctrl[new_index])); | 1611 | 163k | new_ctrl[new_index] = static_cast<ctrl_t>(H2(hash)); | 1612 | 163k | const size_t slot_size = policy_.slot_size; | 1613 | 163k | void* new_slot = SlotAddress(new_slots_, new_index, slot_size); | 1614 | 163k | void* old_slot = SlotAddress(old_slots_, source_offset, slot_size); | 1615 | 163k | SanitizerUnpoisonMemoryRegion(new_slot, slot_size); | 1616 | 163k | policy_.transfer_n(&common_, new_slot, old_slot, 1); | 1617 | 163k | SanitizerPoisonMemoryRegion(old_slot, slot_size); | 1618 | 163k | return; | 1619 | 163k | } | 1620 | 163k | } | 1621 | 42.5k | EncodeItem(ProbedItem(H2(hash), source_offset, h1)); | 1622 | 42.5k | } |
Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false>::EncodeProbedElement(unsigned long, unsigned long) Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false>::EncodeProbedElement(unsigned long, unsigned long) |
1623 | | |
1624 | | // Decodes information about probed elements from all available sources. |
1625 | | // Finds new position for each element and transfers it to the new slots. |
1626 | | // Returns the total probe length. |
1627 | 381k | size_t DecodeAndInsertToTable() const { |
1628 | 381k | if (pos_ == buffer_) { |
1629 | 277k | return 0; |
1630 | 277k | } |
1631 | 103k | if constexpr (kGuaranteedFitToBuffer) { |
1632 | 93.9k | return DecodeAndInsertImpl(common_, policy_, buffer_, pos_, old_slots_); |
1633 | 93.9k | } |
1634 | 0 | size_t total_probe_length = DecodeAndInsertImpl( |
1635 | 103k | common_, policy_, buffer_, |
1636 | 103k | local_buffer_full_ ? buffer_ + kBufferSize : pos_, old_slots_); |
1637 | 103k | if (!local_buffer_full_) { |
1638 | 9.32k | return total_probe_length; |
1639 | 9.32k | } |
1640 | 93.9k | total_probe_length += DecodeAndInsertToTableOverflow(); |
1641 | 93.9k | return total_probe_length; |
1642 | 103k | } raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true>::DecodeAndInsertToTable() const Line | Count | Source | 1627 | 362k | size_t DecodeAndInsertToTable() const { | 1628 | 362k | if (pos_ == buffer_) { | 1629 | 268k | return 0; | 1630 | 268k | } | 1631 | 93.9k | if constexpr (kGuaranteedFitToBuffer) { | 1632 | 93.9k | return DecodeAndInsertImpl(common_, policy_, buffer_, pos_, old_slots_); | 1633 | 93.9k | } | 1634 | 0 | size_t total_probe_length = DecodeAndInsertImpl( | 1635 | 93.9k | common_, policy_, buffer_, | 1636 | 93.9k | local_buffer_full_ ? buffer_ + kBufferSize : pos_, old_slots_); | 1637 | 93.9k | if (!local_buffer_full_) { | 1638 | 0 | return total_probe_length; | 1639 | 0 | } | 1640 | 93.9k | total_probe_length += DecodeAndInsertToTableOverflow(); | 1641 | 93.9k | return total_probe_length; | 1642 | 93.9k | } |
raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false>::DecodeAndInsertToTable() const Line | Count | Source | 1627 | 18.7k | size_t DecodeAndInsertToTable() const { | 1628 | 18.7k | if (pos_ == buffer_) { | 1629 | 9.40k | return 0; | 1630 | 9.40k | } | 1631 | | if constexpr (kGuaranteedFitToBuffer) { | 1632 | | return DecodeAndInsertImpl(common_, policy_, buffer_, pos_, old_slots_); | 1633 | | } | 1634 | 9.32k | size_t total_probe_length = DecodeAndInsertImpl( | 1635 | 9.32k | common_, policy_, buffer_, | 1636 | 9.32k | local_buffer_full_ ? buffer_ + kBufferSize : pos_, old_slots_); | 1637 | 9.32k | if (!local_buffer_full_) { | 1638 | 9.32k | return total_probe_length; | 1639 | 9.32k | } | 1640 | 0 | total_probe_length += DecodeAndInsertToTableOverflow(); | 1641 | 0 | return total_probe_length; | 1642 | 9.32k | } |
Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false>::DecodeAndInsertToTable() const Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false>::DecodeAndInsertToTable() const |
1643 | | |
1644 | | private: |
1645 | | // Encode item into the best available location. |
1646 | 217k | void EncodeItem(ProbedItem item) { |
1647 | 217k | if (ABSL_PREDICT_FALSE(!kGuaranteedFitToBuffer && pos_ >= end_)) { |
1648 | 0 | return ProcessEncodeWithOverflow(item); |
1649 | 0 | } |
1650 | 217k | ABSL_SWISSTABLE_ASSERT(pos_ < end_); |
1651 | 217k | *pos_ = item; |
1652 | 217k | ++pos_; |
1653 | 217k | } raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true>::EncodeItem(absl::container_internal::ProbedItemImpl<unsigned int, 32ul>) Line | Count | Source | 1646 | 175k | void EncodeItem(ProbedItem item) { | 1647 | 175k | if (ABSL_PREDICT_FALSE(!kGuaranteedFitToBuffer && pos_ >= end_)) { | 1648 | 0 | return ProcessEncodeWithOverflow(item); | 1649 | 0 | } | 1650 | 175k | ABSL_SWISSTABLE_ASSERT(pos_ < end_); | 1651 | 175k | *pos_ = item; | 1652 | 175k | ++pos_; | 1653 | 175k | } |
raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false>::EncodeItem(absl::container_internal::ProbedItemImpl<unsigned int, 32ul>) Line | Count | Source | 1646 | 42.5k | void EncodeItem(ProbedItem item) { | 1647 | 42.5k | if (ABSL_PREDICT_FALSE(!kGuaranteedFitToBuffer && pos_ >= end_)) { | 1648 | 0 | return ProcessEncodeWithOverflow(item); | 1649 | 0 | } | 1650 | 42.5k | ABSL_SWISSTABLE_ASSERT(pos_ < end_); | 1651 | 42.5k | *pos_ = item; | 1652 | 42.5k | ++pos_; | 1653 | 42.5k | } |
Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false>::EncodeItem(absl::container_internal::ProbedItemImpl<unsigned long, 64ul>) Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false>::EncodeItem(absl::container_internal::ProbedItemImpl<unsigned long, 122ul>) |
1654 | | |
1655 | 0 | static ProbedItem* AlignToNextItem(void* ptr) { |
1656 | 0 | return reinterpret_cast<ProbedItem*>(AlignUpTo( |
1657 | 0 | reinterpret_cast<uintptr_t>(ptr), alignof(ProbedItem))); |
1658 | 0 | } Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false>::AlignToNextItem(void*) Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false>::AlignToNextItem(void*) Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false>::AlignToNextItem(void*) Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true>::AlignToNextItem(void*) |
1659 | | |
1660 | 0 | ProbedItem* OverflowBufferStart() const { |
1661 | 0 | ABSL_SWISSTABLE_ASSERT(!kGuaranteedFitToBuffer && |
1662 | 0 | "OverflowBufferStart should not be called when " |
1663 | 0 | "kGuaranteedFitToBuffer is true."); |
1664 | | // We reuse GrowthInfo memory as well. |
1665 | 0 | return AlignToNextItem( |
1666 | 0 | control_ - MetadataBeforeControlSize(/*has_infoz=*/false, |
1667 | 0 | NextCapacity(kMaxLocalBufferOldCapacity))); |
1668 | 0 | } Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false>::OverflowBufferStart() const Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false>::OverflowBufferStart() const Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false>::OverflowBufferStart() const Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true>::OverflowBufferStart() const |
1669 | | |
1670 | | // Encodes item when previously allocated buffer is full. |
1671 | | // At first that happens when local buffer is full. |
1672 | | // We switch from the local buffer to the control buffer. |
1673 | | // Every time this function is called, the available buffer is extended till |
1674 | | // `item.source_offset` byte in the control buffer. |
1675 | | // After the buffer is extended, this function wouldn't be called till the |
1676 | | // buffer is exhausted. |
1677 | | // |
1678 | | // If there's no space in the control buffer, we fallback to naive algorithm |
1679 | | // and mark probed elements as kMarkedForSlowTransfer in the control buffer. |
1680 | | // In this case, we will call this function for every subsequent probed |
1681 | | // element. |
1682 | 0 | ABSL_ATTRIBUTE_NOINLINE void ProcessEncodeWithOverflow(ProbedItem item) { |
1683 | 0 | if (!local_buffer_full_) { |
1684 | 0 | local_buffer_full_ = true; |
1685 | 0 | pos_ = OverflowBufferStart(); |
1686 | 0 | } |
1687 | 0 | const size_t source_offset = static_cast<size_t>(item.source_offset); |
1688 | | // We are in fallback mode so we can't reuse control buffer anymore. |
1689 | | // Probed elements are marked as kMarkedForSlowTransfer in the control |
1690 | | // buffer. |
1691 | 0 | if (ABSL_PREDICT_FALSE(marked_elements_starting_position_ != |
1692 | 0 | kNoMarkedElementsSentinel)) { |
1693 | 0 | control_[source_offset] = ctrl_t::kMarkedForSlowTransfer; |
1694 | 0 | return; |
1695 | 0 | } |
1696 | | // Refresh the end pointer to the new available position. |
1697 | | // Invariant: if pos < end, then we have at least sizeof(ProbedItem) bytes |
1698 | | // to write. |
1699 | 0 | end_ = control_ + source_offset + 1 - sizeof(ProbedItem); |
1700 | 0 | if (ABSL_PREDICT_TRUE(pos_ < end_)) { |
1701 | 0 | *pos_ = item; |
1702 | 0 | ++pos_; |
1703 | 0 | return; |
1704 | 0 | } |
1705 | 0 | control_[source_offset] = ctrl_t::kMarkedForSlowTransfer; |
1706 | 0 | marked_elements_starting_position_ = source_offset; |
1707 | | // Now we will always fall down to `ProcessEncodeWithOverflow`. |
1708 | 0 | ABSL_SWISSTABLE_ASSERT(pos_ >= end_); |
1709 | 0 | } Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false>::ProcessEncodeWithOverflow(absl::container_internal::ProbedItemImpl<unsigned int, 32ul>) Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false>::ProcessEncodeWithOverflow(absl::container_internal::ProbedItemImpl<unsigned long, 64ul>) Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false>::ProcessEncodeWithOverflow(absl::container_internal::ProbedItemImpl<unsigned long, 122ul>) Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true>::ProcessEncodeWithOverflow(absl::container_internal::ProbedItemImpl<unsigned int, 32ul>) |
1710 | | |
1711 | | // Decodes information about probed elements from control buffer and processes |
1712 | | // marked elements. |
1713 | | // Finds new position for each element and transfers it to the new slots. |
1714 | | // Returns the total probe length. |
1715 | 0 | ABSL_ATTRIBUTE_NOINLINE size_t DecodeAndInsertToTableOverflow() const { |
1716 | 0 | ABSL_SWISSTABLE_ASSERT(local_buffer_full_ && |
1717 | 0 | "must not be called when local buffer is not full"); |
1718 | 0 | size_t total_probe_length = DecodeAndInsertImpl( |
1719 | 0 | common_, policy_, OverflowBufferStart(), pos_, old_slots_); |
1720 | 0 | if (ABSL_PREDICT_TRUE(marked_elements_starting_position_ == |
1721 | 0 | kNoMarkedElementsSentinel)) { |
1722 | 0 | return total_probe_length; |
1723 | 0 | } |
1724 | 0 | total_probe_length += |
1725 | 0 | ProcessProbedMarkedElements(common_, policy_, control_, old_slots_, |
1726 | 0 | marked_elements_starting_position_); |
1727 | 0 | return total_probe_length; |
1728 | 0 | } Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false>::DecodeAndInsertToTableOverflow() const Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false>::DecodeAndInsertToTableOverflow() const Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false>::DecodeAndInsertToTableOverflow() const Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true>::DecodeAndInsertToTableOverflow() const |
1729 | | |
1730 | | static constexpr size_t kBufferSize = |
1731 | | kProbedElementsBufferSize / sizeof(ProbedItem); |
1732 | | CommonFields& common_; |
1733 | | const PolicyFunctions& policy_; |
1734 | | ctrl_t* const control_; |
1735 | | void* const old_slots_; |
1736 | | const size_t new_capacity_; |
1737 | | ctrl_t* const new_ctrl_; |
1738 | | void* const new_slots_; |
1739 | | // If local_buffer_full_ is false, then pos_/end_ are in the local buffer, |
1740 | | // otherwise, they're in the overflow buffer. |
1741 | | ProbedItem* pos_ = buffer_; |
1742 | | const void* end_ = buffer_ + kBufferSize; |
1743 | | size_t marked_elements_starting_position_ = kNoMarkedElementsSentinel; |
1744 | | bool local_buffer_full_ = false; |
1745 | | ProbedItem buffer_[kBufferSize]; |
1746 | | }; |
1747 | | |
1748 | | // Grows to next capacity with specified encoder type. |
1749 | | // Encoder is used to store probed elements that are processed later. |
1750 | | // Different encoder is used depending on the capacity of the table. |
1751 | | // Returns total probe length. |
1752 | | template <typename Encoder> |
1753 | | size_t GrowToNextCapacity(CommonFields& common, |
1754 | | const PolicyFunctions& __restrict policy, |
1755 | | ctrl_t* old_ctrl, void* old_slots, |
1756 | 381k | size_t new_capacity) { |
1757 | 381k | ABSL_SWISSTABLE_ASSERT(common.capacity() == new_capacity); |
1758 | 381k | ABSL_SWISSTABLE_ASSERT(new_capacity <= Encoder::ProbedItem::kMaxNewCapacity); |
1759 | 381k | Encoder encoder(common, policy, old_ctrl, old_slots, new_capacity); |
1760 | 381k | policy.transfer_unprobed_elements_to_next_capacity( |
1761 | 381k | common, old_ctrl, &encoder, old_slots, PreviousCapacity(new_capacity), |
1762 | 545k | [](void* probed_storage, size_t hash, size_t source_offset) { |
1763 | 545k | auto encoder_ptr = static_cast<Encoder*>(probed_storage); |
1764 | 545k | encoder_ptr->EncodeProbedElement(hash, source_offset); |
1765 | 545k | }); raw_hash_set.cc:absl::container_internal::(anonymous namespace)::GrowToNextCapacity<absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long)::{lambda(void*, unsigned long, unsigned long)#1}::operator()(void*, unsigned long, unsigned long) constLine | Count | Source | 1762 | 339k | [](void* probed_storage, size_t hash, size_t source_offset) { | 1763 | 339k | auto encoder_ptr = static_cast<Encoder*>(probed_storage); | 1764 | 339k | encoder_ptr->EncodeProbedElement(hash, source_offset); | 1765 | 339k | }); |
raw_hash_set.cc:absl::container_internal::(anonymous namespace)::GrowToNextCapacity<absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long)::{lambda(void*, unsigned long, unsigned long)#1}::operator()(void*, unsigned long, unsigned long) constLine | Count | Source | 1762 | 205k | [](void* probed_storage, size_t hash, size_t source_offset) { | 1763 | 205k | auto encoder_ptr = static_cast<Encoder*>(probed_storage); | 1764 | 205k | encoder_ptr->EncodeProbedElement(hash, source_offset); | 1765 | 205k | }); |
Unexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::GrowToNextCapacity<absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long)::{lambda(void*, unsigned long, unsigned long)#1}::operator()(void*, unsigned long, unsigned long) constUnexecuted instantiation: raw_hash_set.cc:absl::container_internal::(anonymous namespace)::GrowToNextCapacity<absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long)::{lambda(void*, unsigned long, unsigned long)#1}::operator()(void*, unsigned long, unsigned long) const |
1766 | 381k | InitializeMirroredControlBytes(common.control(), new_capacity); |
1767 | 381k | return encoder.DecodeAndInsertToTable(); |
1768 | 381k | } raw_hash_set.cc:unsigned long absl::container_internal::(anonymous namespace)::GrowToNextCapacity<absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, true> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long) Line | Count | Source | 1756 | 362k | size_t new_capacity) { | 1757 | 362k | ABSL_SWISSTABLE_ASSERT(common.capacity() == new_capacity); | 1758 | 362k | ABSL_SWISSTABLE_ASSERT(new_capacity <= Encoder::ProbedItem::kMaxNewCapacity); | 1759 | 362k | Encoder encoder(common, policy, old_ctrl, old_slots, new_capacity); | 1760 | 362k | policy.transfer_unprobed_elements_to_next_capacity( | 1761 | 362k | common, old_ctrl, &encoder, old_slots, PreviousCapacity(new_capacity), | 1762 | 362k | [](void* probed_storage, size_t hash, size_t source_offset) { | 1763 | 362k | auto encoder_ptr = static_cast<Encoder*>(probed_storage); | 1764 | 362k | encoder_ptr->EncodeProbedElement(hash, source_offset); | 1765 | 362k | }); | 1766 | 362k | InitializeMirroredControlBytes(common.control(), new_capacity); | 1767 | 362k | return encoder.DecodeAndInsertToTable(); | 1768 | 362k | } |
raw_hash_set.cc:unsigned long absl::container_internal::(anonymous namespace)::GrowToNextCapacity<absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned int, 32ul>, false> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long) Line | Count | Source | 1756 | 18.7k | size_t new_capacity) { | 1757 | 18.7k | ABSL_SWISSTABLE_ASSERT(common.capacity() == new_capacity); | 1758 | 18.7k | ABSL_SWISSTABLE_ASSERT(new_capacity <= Encoder::ProbedItem::kMaxNewCapacity); | 1759 | 18.7k | Encoder encoder(common, policy, old_ctrl, old_slots, new_capacity); | 1760 | 18.7k | policy.transfer_unprobed_elements_to_next_capacity( | 1761 | 18.7k | common, old_ctrl, &encoder, old_slots, PreviousCapacity(new_capacity), | 1762 | 18.7k | [](void* probed_storage, size_t hash, size_t source_offset) { | 1763 | 18.7k | auto encoder_ptr = static_cast<Encoder*>(probed_storage); | 1764 | 18.7k | encoder_ptr->EncodeProbedElement(hash, source_offset); | 1765 | 18.7k | }); | 1766 | 18.7k | InitializeMirroredControlBytes(common.control(), new_capacity); | 1767 | 18.7k | return encoder.DecodeAndInsertToTable(); | 1768 | 18.7k | } |
Unexecuted instantiation: raw_hash_set.cc:unsigned long absl::container_internal::(anonymous namespace)::GrowToNextCapacity<absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 64ul>, false> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long) Unexecuted instantiation: raw_hash_set.cc:unsigned long absl::container_internal::(anonymous namespace)::GrowToNextCapacity<absl::container_internal::(anonymous namespace)::ProbedItemEncoder<absl::container_internal::ProbedItemImpl<unsigned long, 122ul>, false> >(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, absl::container_internal::ctrl_t*, void*, unsigned long) |
1769 | | |
1770 | | // Grows to next capacity for relatively small tables so that even if all |
1771 | | // elements are probed, we don't need to overflow the local buffer. |
1772 | | // Returns total probe length. |
1773 | | size_t GrowToNextCapacityThatFitsInLocalBuffer( |
1774 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
1775 | 362k | ctrl_t* old_ctrl, void* old_slots, size_t new_capacity) { |
1776 | 362k | ABSL_SWISSTABLE_ASSERT(new_capacity <= kMaxLocalBufferNewCapacity); |
1777 | 362k | return GrowToNextCapacity< |
1778 | 362k | ProbedItemEncoder<ProbedItem4Bytes, /*kGuaranteedFitToBuffer=*/true>>( |
1779 | 362k | common, policy, old_ctrl, old_slots, new_capacity); |
1780 | 362k | } |
1781 | | |
1782 | | // Grows to next capacity with different encodings. Returns total probe length. |
1783 | | // These functions are useful to simplify profile analysis. |
1784 | | size_t GrowToNextCapacity4BytesEncoder(CommonFields& common, |
1785 | | const PolicyFunctions& __restrict policy, |
1786 | | ctrl_t* old_ctrl, void* old_slots, |
1787 | 18.7k | size_t new_capacity) { |
1788 | 18.7k | return GrowToNextCapacity<ProbedItemEncoder<ProbedItem4Bytes>>( |
1789 | 18.7k | common, policy, old_ctrl, old_slots, new_capacity); |
1790 | 18.7k | } |
1791 | | size_t GrowToNextCapacity8BytesEncoder(CommonFields& common, |
1792 | | const PolicyFunctions& __restrict policy, |
1793 | | ctrl_t* old_ctrl, void* old_slots, |
1794 | 0 | size_t new_capacity) { |
1795 | 0 | return GrowToNextCapacity<ProbedItemEncoder<ProbedItem8Bytes>>( |
1796 | 0 | common, policy, old_ctrl, old_slots, new_capacity); |
1797 | 0 | } |
1798 | | size_t GrowToNextCapacity16BytesEncoder( |
1799 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
1800 | 0 | ctrl_t* old_ctrl, void* old_slots, size_t new_capacity) { |
1801 | 0 | return GrowToNextCapacity<ProbedItemEncoder<ProbedItem16Bytes>>( |
1802 | 0 | common, policy, old_ctrl, old_slots, new_capacity); |
1803 | 0 | } |
1804 | | |
1805 | | // Grows to next capacity for tables with relatively large capacity so that we |
1806 | | // can't guarantee that all probed elements fit in the local buffer. Returns |
1807 | | // total probe length. |
1808 | | size_t GrowToNextCapacityOverflowLocalBuffer( |
1809 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
1810 | 18.7k | ctrl_t* old_ctrl, void* old_slots, size_t new_capacity) { |
1811 | 18.7k | ABSL_SWISSTABLE_ASSERT(common.capacity() == new_capacity); |
1812 | 18.7k | if (ABSL_PREDICT_TRUE(new_capacity <= ProbedItem4Bytes::kMaxNewCapacity)) { |
1813 | 18.7k | return GrowToNextCapacity4BytesEncoder(common, policy, old_ctrl, old_slots, |
1814 | 18.7k | new_capacity); |
1815 | 18.7k | } |
1816 | 0 | if (ABSL_PREDICT_TRUE(new_capacity <= ProbedItem8Bytes::kMaxNewCapacity)) { |
1817 | 0 | return GrowToNextCapacity8BytesEncoder(common, policy, old_ctrl, old_slots, |
1818 | 0 | new_capacity); |
1819 | 0 | } |
1820 | | // 16 bytes encoding supports the maximum swisstable capacity. |
1821 | 0 | return GrowToNextCapacity16BytesEncoder(common, policy, old_ctrl, old_slots, |
1822 | 0 | new_capacity); |
1823 | 0 | } |
1824 | | |
1825 | | // Dispatches to the appropriate `GrowToNextCapacity*` function based on the |
1826 | | // capacity of the table. Returns total probe length. |
1827 | | ABSL_ATTRIBUTE_NOINLINE |
1828 | | size_t GrowToNextCapacityDispatch(CommonFields& common, |
1829 | | const PolicyFunctions& __restrict policy, |
1830 | | ctrl_t* old_ctrl, void* old_slots, |
1831 | 381k | size_t new_capacity) { |
1832 | 381k | ABSL_SWISSTABLE_ASSERT(common.capacity() == new_capacity); |
1833 | 381k | if (ABSL_PREDICT_TRUE(new_capacity <= kMaxLocalBufferNewCapacity)) { |
1834 | 362k | return GrowToNextCapacityThatFitsInLocalBuffer(common, policy, old_ctrl, |
1835 | 362k | old_slots, new_capacity); |
1836 | 362k | } else { |
1837 | 18.7k | return GrowToNextCapacityOverflowLocalBuffer(common, policy, old_ctrl, |
1838 | 18.7k | old_slots, new_capacity); |
1839 | 18.7k | } |
1840 | 381k | } |
1841 | | |
1842 | | void IncrementSmallSizeNonSoo(CommonFields& common, |
1843 | 220k | const PolicyFunctions& __restrict policy) { |
1844 | 220k | ABSL_SWISSTABLE_ASSERT(common.is_small()); |
1845 | 220k | common.increment_size(); |
1846 | 220k | SanitizerUnpoisonMemoryRegion( |
1847 | 220k | SingleSlotAddress</*kSooEnabled=*/false>(common), policy.slot_size); |
1848 | 220k | } |
1849 | | |
1850 | | void IncrementSmallSize(CommonFields& common, |
1851 | 0 | const PolicyFunctions& __restrict policy) { |
1852 | 0 | ABSL_SWISSTABLE_ASSERT(common.is_small()); |
1853 | 0 | if (policy.soo_enabled) { |
1854 | 0 | common.set_full_soo(); |
1855 | 0 | } else { |
1856 | 0 | IncrementSmallSizeNonSoo(common, policy); |
1857 | 0 | } |
1858 | 0 | } |
1859 | | |
1860 | | void* Grow1To3AndPrepareInsert(CommonFields& common, |
1861 | | const PolicyFunctions& __restrict policy, |
1862 | | void* hasher, HashForKeyFn get_hash, void* alloc, |
1863 | 257k | bool has_infoz, void* new_slots) { |
1864 | | // TODO(b/413062340): Refactor to reuse more code with |
1865 | | // GrowSooTableToNextCapacityAndPrepareInsert. |
1866 | 257k | ABSL_SWISSTABLE_ASSERT(common.capacity() == 1); |
1867 | 257k | ABSL_SWISSTABLE_ASSERT(!common.empty()); |
1868 | 257k | ABSL_SWISSTABLE_ASSERT(!policy.soo_enabled); |
1869 | | // 1-element tables can't have any blocked elements. |
1870 | 257k | ABSL_SWISSTABLE_ASSERT(common.blocked_element_count() == 0); |
1871 | 257k | constexpr size_t kOldCapacity = 1; |
1872 | 257k | constexpr size_t kNewCapacity = NextCapacity(kOldCapacity); |
1873 | 257k | ctrl_t* new_ctrl = |
1874 | 257k | static_cast<ctrl_t*>(new_slots) - NumControlBytes(kNewCapacity); |
1875 | | |
1876 | 257k | if (ABSL_PREDICT_TRUE(!has_infoz)) { |
1877 | | // When we're sampled, we already have a seed. |
1878 | 257k | common.generate_new_seed(/*has_infoz=*/false); |
1879 | 257k | } |
1880 | 257k | const size_t new_hash = get_hash(hasher, common.seed().seed()); |
1881 | 257k | h2_t new_h2 = H2(new_hash); |
1882 | 257k | size_t orig_hash = |
1883 | 257k | policy.hash_slot(policy.hash_fn(common), common.single_non_soo_slot(), |
1884 | 257k | common.seed().seed()); |
1885 | 257k | size_t offset = Resize1To3NewOffset(new_hash); |
1886 | 257k | InitializeThreeElementsControlBytes(H2(orig_hash), new_h2, offset, new_ctrl); |
1887 | | |
1888 | | // Read `old_slots` and `slot_size` after the hash calls to avoid keeping |
1889 | | // them live across indirect calls. |
1890 | 257k | void* old_slots = common.single_non_soo_slot(); |
1891 | | // old_slots == old_ctrl in case of capacity == 1. |
1892 | 257k | ctrl_t* old_ctrl = static_cast<ctrl_t*>(old_slots); |
1893 | 257k | common.set_control(new_ctrl); |
1894 | 257k | const size_t slot_size = policy.slot_size; |
1895 | 257k | SanitizerPoisonMemoryRegion(new_slots, kNewCapacity * slot_size); |
1896 | | |
1897 | 257k | void* old_element_target = NextSlot(new_slots, slot_size); |
1898 | | // Compute `new_element_target_slot` before `transfer_n` to free up registers |
1899 | | // of `new_slots` and `offset`. |
1900 | 257k | void* new_element_target_slot = SlotAddress(new_slots, offset, slot_size); |
1901 | 257k | SanitizerUnpoisonMemoryRegion(old_element_target, slot_size); |
1902 | 257k | policy.transfer_n(&common, old_element_target, old_slots, 1); |
1903 | 257k | SanitizerUnpoisonMemoryRegion(new_element_target_slot, slot_size); |
1904 | | |
1905 | | // Group `common` updates together so `inline_data_` is modified in a single |
1906 | | // load-store sequence. |
1907 | 257k | common.set_capacity(kNewCapacity); |
1908 | 257k | common.InitGrowthLeftNoDeleted(kNewCapacity - 2, kNewCapacity); |
1909 | 257k | PrepareInsertCommon(common); |
1910 | 257k | ABSL_SWISSTABLE_ASSERT(common.size() == 2); |
1911 | | |
1912 | 257k | if (ABSL_PREDICT_FALSE(has_infoz)) { |
1913 | 0 | ReportSingleGroupTableGrowthToInfoz(common, old_ctrl, new_hash); |
1914 | 0 | } |
1915 | | // Deallocate after `ReportSingleGroupTableGrowthToInfoz`, because it reads |
1916 | | // infoz from old_ctrl. |
1917 | 257k | DeallocBackingArrayImpl(alloc, kOldCapacity, old_ctrl, slot_size, |
1918 | 257k | policy.slot_align_mask, has_infoz, |
1919 | 257k | /*blocked_element_count=*/0, policy.dealloc); |
1920 | 257k | return new_element_target_slot; |
1921 | 257k | } |
1922 | | |
1923 | | // Grows to next capacity and prepares insert for the given new_hash. |
1924 | | // Returns the offset of the new element. |
1925 | | void* GrowToNextCapacityAndPrepareInsert( |
1926 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
1927 | 855k | size_t new_hash, size_t old_capacity) { |
1928 | 855k | ABSL_SWISSTABLE_ASSERT(common.capacity() == old_capacity); |
1929 | 855k | ABSL_SWISSTABLE_ASSERT(common.GetGrowthLeftTotalSlow(old_capacity) == 0); |
1930 | 855k | ABSL_SWISSTABLE_ASSERT(old_capacity > policy.soo_capacity()); |
1931 | 855k | ABSL_SWISSTABLE_ASSERT(!IsSmallCapacity(old_capacity)); |
1932 | 855k | ABSL_ASSUME(old_capacity > kMaxSmallCapacity); |
1933 | | |
1934 | 855k | const size_t new_capacity = NextCapacity(old_capacity); |
1935 | 855k | ABSL_ASSUME(new_capacity > kMaxSmallCapacity); |
1936 | | |
1937 | 855k | void* alloc = policy.get_char_alloc(common); |
1938 | 855k | const auto [new_ctrl, new_slots] = |
1939 | 855k | AllocBackingArray(common, policy, new_capacity, common.has_infoz(), alloc, |
1940 | 855k | /*blocked_element_count=*/0); |
1941 | | // Read old `common` fields after `AllocBackingArray` to avoid spilling them |
1942 | | // across the call. |
1943 | 855k | ctrl_t* old_ctrl = common.control(); |
1944 | 855k | void* old_slots = common.slot_array(old_capacity); |
1945 | 855k | size_t old_blocked_element_count = common.blocked_element_count(); |
1946 | 855k | const size_t old_size = common.size(); |
1947 | | |
1948 | | // Group all `common` updates together so `inline_data_` is modified in a |
1949 | | // single load-store sequence. |
1950 | 855k | common.set_capacity(new_capacity); |
1951 | 855k | common.set_blocked_element_count_to_zero(); |
1952 | 855k | common.set_control(new_ctrl); |
1953 | 855k | PrepareInsertCommon(common); |
1954 | 855k | ResetGrowthLeft(new_capacity, old_size + 1, common); |
1955 | 855k | const size_t slot_size = policy.slot_size; |
1956 | 855k | SanitizerPoisonMemoryRegion(new_slots, new_capacity * slot_size); |
1957 | | |
1958 | 855k | h2_t new_h2 = H2(new_hash); |
1959 | 855k | size_t total_probe_length = 0; |
1960 | 855k | FindInfo find_info; |
1961 | 855k | if (ABSL_PREDICT_TRUE(is_single_group(new_capacity))) { |
1962 | 474k | size_t offset; |
1963 | 474k | GrowIntoSingleGroupShuffleControlBytes(old_ctrl, old_capacity, |
1964 | 474k | old_blocked_element_count, new_ctrl, |
1965 | 474k | new_capacity); |
1966 | | // We put the new element either at the beginning or at the end of the |
1967 | | // table with approximately equal probability. |
1968 | 474k | offset = new_hash & 1 ? 0 : new_capacity - 1; |
1969 | | |
1970 | 474k | ABSL_SWISSTABLE_ASSERT(IsEmpty(new_ctrl[offset])); |
1971 | 474k | SetCtrlInSingleGroupTable(common, new_capacity, new_ctrl, offset, new_h2, |
1972 | 474k | slot_size); |
1973 | 474k | find_info = FindInfo{offset, 0}; |
1974 | | // Single group tables have all slots full on resize. So we can transfer |
1975 | | // all slots without checking the control bytes. |
1976 | 474k | ABSL_SWISSTABLE_ASSERT(old_size + old_blocked_element_count == |
1977 | 474k | old_capacity); |
1978 | 474k | void* target = NextSlot(new_slots, slot_size); |
1979 | 474k | SanitizerUnpoisonMemoryRegion(target, old_size * slot_size); |
1980 | 474k | policy.transfer_n(&common, target, old_slots, old_size); |
1981 | 474k | } else { |
1982 | 381k | total_probe_length = GrowToNextCapacityDispatch(common, policy, old_ctrl, |
1983 | 381k | old_slots, new_capacity); |
1984 | 381k | find_info = find_first_non_full(new_capacity, new_ctrl, new_hash); |
1985 | 381k | SetCtrl(common, new_capacity, new_ctrl, find_info.offset, new_h2, |
1986 | 381k | slot_size); |
1987 | 381k | } |
1988 | 855k | ABSL_SWISSTABLE_ASSERT(old_capacity > policy.soo_capacity()); |
1989 | | |
1990 | | // Reload `has_infoz` to avoid spilling across the indirect call. |
1991 | 855k | const bool has_infoz = common.has_infoz(); |
1992 | 855k | if (ABSL_PREDICT_FALSE(has_infoz)) { |
1993 | 0 | ReportGrowthToInfoz(common, old_ctrl, new_hash, total_probe_length, |
1994 | 0 | find_info.probe_length); |
1995 | 0 | } |
1996 | | // Deallocate after `ReportGrowthToInfoz`, because it reads infoz from |
1997 | | // old_ctrl. |
1998 | 855k | DeallocBackingArrayImpl(alloc, old_capacity, old_ctrl, slot_size, |
1999 | 855k | policy.slot_align_mask, has_infoz, |
2000 | 855k | old_blocked_element_count, policy.dealloc); |
2001 | 855k | return SlotAddress(new_slots, find_info.offset, slot_size); |
2002 | 855k | } |
2003 | | |
2004 | | } // namespace |
2005 | | |
2006 | | RawHashSetLayout RawHashSetLayout::CreateForSmallNonSooGrowth( |
2007 | 519k | size_t size, size_t slot_size, size_t slot_align_mask, bool has_infoz) { |
2008 | 519k | ABSL_SWISSTABLE_ASSERT(size <= 1); |
2009 | 519k | static_assert(NumControlBytes(1) == 0); |
2010 | 519k | const size_t num_control_bytes = size * NumControlBytes(3); |
2011 | 519k | static_assert(NextCapacity(0) == 1); |
2012 | 519k | static_assert(NextCapacity(1) == 3); |
2013 | 519k | const size_t new_capacity = NextCapacity(size); |
2014 | 519k | const size_t slots_size = new_capacity * slot_size; |
2015 | | |
2016 | | // Since `GrowthInfoSizeForCapacity` is 0 for capacities 1 and 3, compute |
2017 | | // `metadata_size` directly without `MetadataBeforeControlSize` to avoid its |
2018 | | // `ABSL_PREDICT_FALSE(has_infoz)` branch and allow a branchless shift. |
2019 | 519k | static_assert(GrowthInfoSizeForCapacity(1) == 0); |
2020 | 519k | static_assert(GrowthInfoSizeForCapacity(3) == 0); |
2021 | 519k | const size_t metadata_size = |
2022 | 519k | (has_infoz ? sizeof(HashtablezInfoHandle) + sizeof(uint64_t) : 0) + |
2023 | 519k | NumGenerationBytes(); |
2024 | 519k | ABSL_SWISSTABLE_ASSERT(metadata_size == |
2025 | 519k | MetadataBeforeControlSize(has_infoz, new_capacity)); |
2026 | | |
2027 | 519k | RawHashSetLayout layout(uninitialized_tag_t{}); |
2028 | 519k | layout.slot_offset_ = |
2029 | 519k | AlignUpToMask(metadata_size + num_control_bytes, slot_align_mask); |
2030 | 519k | layout.control_offset_ = layout.slot_offset_ - num_control_bytes; |
2031 | 519k | layout.generation_offset_ = layout.control_offset_ - NumGenerationBytes(); |
2032 | 519k | layout.alloc_size_ = layout.slot_offset_ + slots_size; |
2033 | 519k | return layout; |
2034 | 519k | } |
2035 | | |
2036 | | void* PrepareInsertSmallNonSoo(CommonFields& common, |
2037 | | const PolicyFunctions& __restrict policy, |
2038 | 739k | void* hasher, HashForKeyFn get_hash) { |
2039 | 739k | ABSL_SWISSTABLE_ASSERT(common.is_small()); |
2040 | 739k | ABSL_SWISSTABLE_ASSERT(!policy.soo_enabled); |
2041 | 739k | const bool is_empty = common.empty_when_small(); |
2042 | 739k | if (common.capacity() == 1 && is_empty) { |
2043 | 220k | IncrementSmallSizeNonSoo(common, policy); |
2044 | | // Compute before `RecordInsertMissCold` to avoid reloading `control_`. |
2045 | 220k | void* res = common.single_non_soo_slot(); |
2046 | | // Call NOINLINE function to move infoz instructions out of line. |
2047 | 220k | if (common.has_infoz()) { |
2048 | 0 | ReportInsertMissToInfozAndComputeHash(common, /*probe_length=*/0, hasher, |
2049 | 0 | get_hash); |
2050 | 0 | } |
2051 | 220k | return res; |
2052 | 220k | } |
2053 | | |
2054 | 519k | const size_t size = is_empty ? 0 : 1; |
2055 | 519k | const bool has_infoz = |
2056 | 519k | is_empty ? (policy.is_hashtablez_enabled && ShouldSampleNextTable()) |
2057 | 519k | : common.has_infoz(); |
2058 | 519k | void* alloc = policy.get_char_alloc(common); |
2059 | 519k | void* new_slots = |
2060 | 519k | AllocBackingArray( |
2061 | 519k | common, policy, |
2062 | 519k | RawHashSetLayout::CreateForSmallNonSooGrowth( |
2063 | 519k | size, policy.slot_size, policy.slot_align_mask, has_infoz), |
2064 | 519k | alloc) |
2065 | 519k | .slots; |
2066 | 519k | if (is_empty) { |
2067 | | // Growing from 0 to 1 capacity. |
2068 | 261k | ABSL_SWISSTABLE_ASSERT(common.capacity() == 0); |
2069 | 261k | constexpr size_t kNewCapacity = 1; |
2070 | 261k | common.set_capacity(kNewCapacity); |
2071 | 261k | common.set_control(static_cast<ctrl_t*>(new_slots)); |
2072 | | |
2073 | 261k | static_assert(NextCapacity(0) == 1); |
2074 | 261k | PrepareInsertCommon(common); |
2075 | | |
2076 | 261k | if (ABSL_PREDICT_FALSE(has_infoz)) { |
2077 | 0 | ReportSingleElementTableGrowthToInfozAndForceSample( |
2078 | 0 | common, policy, hasher, get_hash); |
2079 | 0 | } |
2080 | 261k | return new_slots; |
2081 | 261k | } |
2082 | 257k | return Grow1To3AndPrepareInsert(common, policy, hasher, get_hash, alloc, |
2083 | 257k | has_infoz, new_slots); |
2084 | 519k | } |
2085 | | |
2086 | | namespace { |
2087 | | |
2088 | | // Called whenever the table needs to vacate empty slots either by removing |
2089 | | // tombstones via rehash or growth to next capacity. |
2090 | | ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void* |
2091 | | RehashOrGrowToNextCapacityAndPrepareInsert( |
2092 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
2093 | 0 | size_t new_hash, size_t cap) { |
2094 | 0 | ABSL_SWISSTABLE_ASSERT(!common.GetGrowthInfoLowerBound().HasNoDeleted()); |
2095 | 0 | ABSL_SWISSTABLE_ASSERT(common.capacity() == cap); |
2096 | 0 | ABSL_ASSUME(cap > 0); |
2097 | | // Do these calculations in 64-bit to avoid overflow. |
2098 | 0 | if (common.size() * uint64_t{32} <= |
2099 | 0 | (cap - kMaxBlockedElementsForLargeTables) * uint64_t{25}) { |
2100 | | // Squash DELETED without growing if there is enough capacity. |
2101 | | // |
2102 | | // Rehash in place if the current size is <= 25/32 of capacity. |
2103 | | // Rationale for such a high factor: 1) DropDeletesWithoutResize() is |
2104 | | // faster than resize, and 2) it takes quite a bit of work to add |
2105 | | // tombstones. In the worst case, seems to take approximately 4 |
2106 | | // insert/erase pairs to create a single tombstone and so if we are |
2107 | | // rehashing because of tombstones, we can afford to rehash-in-place as |
2108 | | // long as we are reclaiming at least 1/8 the capacity without doing more |
2109 | | // than 2X the work. (Where "work" is defined to be size() for rehashing |
2110 | | // or rehashing in place, and 1 for an insert or erase.) But rehashing in |
2111 | | // place is faster per operation than inserting or even doubling the size |
2112 | | // of the table, so we actually afford to reclaim even less space from a |
2113 | | // resize-in-place. The decision is to rehash in place if we can reclaim |
2114 | | // at about 1/8th of the usable capacity (specifically 3/28 of the |
2115 | | // capacity) which means that the total cost of rehashing will be a small |
2116 | | // fraction of the total work. |
2117 | | // |
2118 | | // Here is output of an experiment using the BM_CacheInSteadyState |
2119 | | // benchmark running the old case (where we rehash-in-place only if we can |
2120 | | // reclaim at least 7/16*capacity) vs. this code (which rehashes in place |
2121 | | // if we can recover 3/32*capacity). |
2122 | | // |
2123 | | // Note that although in the worst-case number of rehashes jumped up from |
2124 | | // 15 to 190, but the number of operations per second is almost the same. |
2125 | | // |
2126 | | // Abridged output of running BM_CacheInSteadyState benchmark from |
2127 | | // raw_hash_set_benchmark. N is the number of insert/erase operations. |
2128 | | // |
2129 | | // | OLD (recover >= 7/16 | NEW (recover >= 3/32) |
2130 | | // size | N/s LoadFactor NRehashes | N/s LoadFactor NRehashes |
2131 | | // 448 | 145284 0.44 18 | 140118 0.44 19 |
2132 | | // 493 | 152546 0.24 11 | 151417 0.48 28 |
2133 | | // 538 | 151439 0.26 11 | 151152 0.53 38 |
2134 | | // 583 | 151765 0.28 11 | 150572 0.57 50 |
2135 | | // 628 | 150241 0.31 11 | 150853 0.61 66 |
2136 | | // 672 | 149602 0.33 12 | 150110 0.66 90 |
2137 | | // 717 | 149998 0.35 12 | 149531 0.70 129 |
2138 | | // 762 | 149836 0.37 13 | 148559 0.74 190 |
2139 | | // 807 | 149736 0.39 14 | 151107 0.39 14 |
2140 | | // 852 | 150204 0.42 15 | 151019 0.42 15 |
2141 | 0 | return DropDeletesWithoutResizeAndPrepareInsert(common, policy, new_hash, |
2142 | 0 | cap); |
2143 | 0 | } else { |
2144 | | // Otherwise grow the container. |
2145 | 0 | return GrowToNextCapacityAndPrepareInsert(common, policy, new_hash, cap); |
2146 | 0 | } |
2147 | 0 | } |
2148 | | |
2149 | | // Slow path for PrepareInsertLarge that is called when the table has deleted |
2150 | | // slots or need to be resized or rehashed. |
2151 | | ABSL_ATTRIBUTE_NOINLINE |
2152 | | void* PrepareInsertLargeSlow(CommonFields& common, |
2153 | | const PolicyFunctions& __restrict policy, |
2154 | 862k | size_t hash) { |
2155 | 862k | const size_t cap = common.capacity(); |
2156 | 862k | ABSL_ASSUME(cap > kMaxSmallCapacity); |
2157 | 862k | GrowthInfoLowerBound growth_info_lower_bound = |
2158 | 862k | common.RebalanceGrowthLeftLowerBound(cap); |
2159 | 862k | if (ABSL_PREDICT_TRUE( |
2160 | 862k | growth_info_lower_bound.HasNoGrowthLeftAndNoDeleted())) { |
2161 | | // Table without deleted slots (>95% cases) that needs to be resized. |
2162 | 855k | return GrowToNextCapacityAndPrepareInsert(common, policy, hash, cap); |
2163 | 855k | } |
2164 | 6.60k | if (ABSL_PREDICT_FALSE( |
2165 | 6.60k | growth_info_lower_bound.HasNoGrowthLeftAndHaveDeleted())) { |
2166 | | // Table with deleted slots that needs to be rehashed or resized. |
2167 | 0 | return RehashOrGrowToNextCapacityAndPrepareInsert(common, policy, hash, |
2168 | 0 | cap); |
2169 | 0 | } |
2170 | | // Covers two cases: |
2171 | | // 1. Table with deleted slots that has space for the inserting element. |
2172 | | // 2. Table without deleted slots that has space and GrowthInfoView was |
2173 | | // rebalanced. |
2174 | 6.60k | const size_t slot_size = policy.slot_size; |
2175 | 6.60k | ctrl_t* ctrl = common.control(); |
2176 | 6.60k | FindInfo target = find_first_non_full(cap, ctrl, hash); |
2177 | | // Read `ctrl` before `PrepareInsertCommon` so `common` updates coalesce. |
2178 | 6.60k | common.OverwriteControlAsFull(ctrl[target.offset]); |
2179 | 6.60k | PrepareInsertCommon(common); |
2180 | | // Compute before `SetCtrl*` to avoid reloading `control_`. |
2181 | 6.60k | void* res = SlotAddress(common.slot_array(cap), target.offset, slot_size); |
2182 | 6.60k | SetCtrl(common, cap, ctrl, target.offset, H2(hash), slot_size); |
2183 | 6.60k | if (common.has_infoz()) { |
2184 | 0 | ReportInsertMissToInfoz(common, target.probe_length, hash); |
2185 | 0 | } |
2186 | 6.60k | return res; |
2187 | 6.60k | } |
2188 | | |
2189 | | // Resizes empty non-allocated SOO table to NextCapacity(SooCapacity()), |
2190 | | // forces the table to be sampled and prepares the insert. |
2191 | | // SOO tables need to switch from SOO to heap in order to store the infoz. |
2192 | | // Requires: |
2193 | | // 1. `c.capacity() == SooCapacity()`. |
2194 | | // 2. `c.empty()`. |
2195 | | ABSL_ATTRIBUTE_NOINLINE void* |
2196 | | GrowEmptySooTableToNextCapacityForceSamplingAndPrepareInsert( |
2197 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
2198 | 0 | void* hasher, HashForKeyFn get_hash) { |
2199 | 0 | const size_t kNewCapacity = NextCapacity(SooCapacity()); |
2200 | 0 | ResizeEmptyNonAllocatedTableImpl(common, policy, kNewCapacity, |
2201 | 0 | /*blocked_element_count=*/0, |
2202 | 0 | /*force_infoz=*/true); |
2203 | 0 | PrepareInsertCommon(common); |
2204 | 0 | common.OverwriteEmptyAsFull(); |
2205 | 0 | const size_t new_hash = get_hash(hasher, common.seed().seed()); |
2206 | | // Compute before `SetCtrl*` to avoid reloading `control_`. |
2207 | 0 | void* res = SlotAddress(common.slot_array(kNewCapacity), SooSlotIndex(), |
2208 | 0 | policy.slot_size); |
2209 | 0 | SetCtrlInSingleGroupTable(common, kNewCapacity, common.control(), |
2210 | 0 | SooSlotIndex(), H2(new_hash), policy.slot_size); |
2211 | 0 | if (common.has_infoz()) { |
2212 | 0 | ReportInsertMissToInfoz(common, /*probe_length=*/0, new_hash); |
2213 | 0 | } |
2214 | 0 | return res; |
2215 | 0 | } |
2216 | | |
2217 | | // Returns the number of elements to block for the given capacity and reserved |
2218 | | // size. |
2219 | | size_t BlockedElementCountForReservedTable(size_t capacity, |
2220 | 0 | size_t reserved_size) { |
2221 | 0 | if (!IsCapacityValidForBlockedElements(capacity)) { |
2222 | 0 | return 0; |
2223 | 0 | } |
2224 | 0 | const size_t blocked_elements = CapacityToGrowth(capacity) - reserved_size; |
2225 | 0 | if (is_single_group(capacity)) { |
2226 | | // Single group tables never probes, so we can block all the slots. |
2227 | 0 | return blocked_elements; |
2228 | 0 | } |
2229 | 0 | return (std::min)(blocked_elements, kMaxBlockedElementsForLargeTables); |
2230 | 0 | } |
2231 | | |
2232 | | // Resizes empty non-allocated table to the capacity to fit new_size elements. |
2233 | | // Requires: |
2234 | | // 1. `c.capacity() == policy.soo_capacity()`. |
2235 | | // 2. `c.empty()`. |
2236 | | // 3. `new_size > policy.soo_capacity()`. |
2237 | | // The table will be attempted to be sampled. |
2238 | | void ReserveEmptyNonAllocatedTableToFitNewSize( |
2239 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
2240 | 0 | size_t new_size) { |
2241 | 0 | ValidateMaxSize(new_size, policy.key_size, policy.slot_size); |
2242 | 0 | ABSL_ASSUME(new_size > 0); |
2243 | 0 | const size_t new_capacity = SizeToCapacity(new_size); |
2244 | 0 | ResizeEmptyNonAllocatedTableImpl( |
2245 | 0 | common, policy, new_capacity, |
2246 | 0 | BlockedElementCountForReservedTable(new_capacity, new_size), |
2247 | 0 | /*force_infoz=*/false); |
2248 | | // This is after resize, to ensure that we have completed the allocation |
2249 | | // and have potentially sampled the hashtable. |
2250 | 0 | common.infoz().RecordReservation(new_size); |
2251 | 0 | } |
2252 | | |
2253 | | // Type erased version of raw_hash_set::reserve for tables that have an |
2254 | | // allocated backing array. |
2255 | | // |
2256 | | // Requires: |
2257 | | // 1. `c.capacity() > policy.soo_capacity()` OR `!c.empty()`. |
2258 | | // Reserving already allocated tables is considered to be a rare case. |
2259 | | ABSL_ATTRIBUTE_NOINLINE void ReserveAllocatedTable( |
2260 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
2261 | 0 | size_t new_size) { |
2262 | 0 | ABSL_SWISSTABLE_ASSERT(common.capacity() > policy.soo_capacity() || |
2263 | 0 | !common.empty()); |
2264 | 0 | ValidateMaxSize(new_size, policy.key_size, policy.slot_size); |
2265 | 0 | ABSL_ASSUME(new_size > 0); |
2266 | 0 | const size_t new_capacity = SizeToCapacity(new_size); |
2267 | | // TODO(b/382423690): consider using GrowToNextCapacity, when applicable. |
2268 | 0 | ResizeTableWithSeedChangeImpl(common, policy, new_capacity, |
2269 | 0 | /*force_sample_soo=*/false); |
2270 | 0 | common.infoz().RecordReservation(new_size); |
2271 | 0 | } |
2272 | | |
2273 | | // Grows a full SOO table to the next capacity and forces it to be sampled. SOO |
2274 | | // tables need to switch from SOO to heap in order to store the infoz. No-op if |
2275 | | // sampling is disabled or not possible. |
2276 | | void GrowFullSooTableToNextCapacityForceSampling( |
2277 | 0 | CommonFields& common, const PolicyFunctions& __restrict policy) { |
2278 | 0 | AssertFullSoo(common, policy); |
2279 | 0 | ResizeTableWithSeedChangeImpl(common, policy, NextCapacity(SooCapacity()), |
2280 | 0 | /*force_sample_soo=*/true); |
2281 | 0 | } |
2282 | | |
2283 | | } // namespace |
2284 | | |
2285 | | void ResizeAllocatedTableWithSeedChange( |
2286 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
2287 | 0 | size_t new_capacity) { |
2288 | 0 | ABSL_SWISSTABLE_ASSERT(common.capacity() > policy.soo_capacity()); |
2289 | 0 | ResizeTableWithSeedChangeImpl(common, policy, new_capacity, |
2290 | 0 | /*force_sample_soo=*/false); |
2291 | 0 | } |
2292 | | |
2293 | | // Resizes a full SOO table to the NextCapacity(SooCapacity()). |
2294 | | template <size_t SooSlotMemcpySize, bool TransferUsesMemcpy> |
2295 | | void* GrowSooTableToNextCapacityAndPrepareInsert( |
2296 | | CommonFields& common, const PolicyFunctions& __restrict policy, |
2297 | 27.5k | void* hasher, HashForKeyFn get_hash, bool force_sampling) { |
2298 | 27.5k | AssertSoo(common, policy); |
2299 | 27.5k | if (ABSL_PREDICT_FALSE(force_sampling)) { |
2300 | | // The table is empty, it is only used for forced sampling of SOO tables. |
2301 | 0 | return GrowEmptySooTableToNextCapacityForceSamplingAndPrepareInsert( |
2302 | 0 | common, policy, hasher, get_hash); |
2303 | 0 | } |
2304 | 27.5k | ABSL_SWISSTABLE_ASSERT(common.size() == policy.soo_capacity()); |
2305 | 27.5k | static constexpr size_t kNewCapacity = NextCapacity(SooCapacity()); |
2306 | | // Generate the seed and compute hashes before `AllocBackingArray` so |
2307 | | // `hasher`, `get_hash`, and `new_ctrl`/`new_slots` do not need to be spilled |
2308 | | // across calls. |
2309 | 27.5k | common.generate_new_seed(/*has_infoz=*/false); |
2310 | 27.5k | const size_t new_hash = get_hash(hasher, common.seed().seed()); |
2311 | 27.5k | const h2_t soo_slot_h2 = H2(policy.hash_slot( |
2312 | 27.5k | policy.hash_fn(common), common.soo_data(), common.seed().seed())); |
2313 | | |
2314 | | // Since the table is not empty, it will not be sampled. |
2315 | | // The decision to sample was already made during the first insertion. |
2316 | | // |
2317 | | // We do not set control and slots in CommonFields yet to avoid overriding |
2318 | | // SOO data. |
2319 | 27.5k | const auto [new_ctrl, new_slots] = |
2320 | 27.5k | AllocBackingArray(common, policy, kNewCapacity, /*has_infoz=*/false, |
2321 | 27.5k | policy.get_char_alloc(common), |
2322 | 27.5k | /*blocked_element_count=*/0); |
2323 | | // Group `common` updates together after `AllocBackingArray` so `inline_data_` |
2324 | | // is modified in a single load-store sequence. |
2325 | 27.5k | common.set_capacity(kNewCapacity); |
2326 | 27.5k | common.InitGrowthLeftNoDeleted(kNewCapacity - 2, kNewCapacity); |
2327 | 27.5k | PrepareInsertCommon(common); |
2328 | 27.5k | ABSL_SWISSTABLE_ASSERT(common.size() == 2); |
2329 | | |
2330 | 27.5k | const size_t offset = Resize1To3NewOffset(new_hash); |
2331 | 27.5k | InitializeThreeElementsControlBytes(soo_slot_h2, H2(new_hash), offset, |
2332 | 27.5k | new_ctrl); |
2333 | | |
2334 | 27.5k | const size_t slot_size = policy.slot_size; |
2335 | 27.5k | SanitizerPoisonMemoryRegion(new_slots, slot_size * kNewCapacity); |
2336 | 27.5k | void* target_slot = SlotAddress(new_slots, SooSlotIndex(), slot_size); |
2337 | 27.5k | void* new_slot = SlotAddress(new_slots, offset, slot_size); |
2338 | 27.5k | SanitizerUnpoisonMemoryRegion(target_slot, slot_size); |
2339 | 27.5k | if constexpr (TransferUsesMemcpy) { |
2340 | | // Target slot is placed at index 1, but capacity is at |
2341 | | // minimum 3. So we are allowed to copy at least twice as much |
2342 | | // memory. |
2343 | 27.5k | static_assert(SooSlotIndex() == 1); |
2344 | 27.5k | static_assert(SooSlotMemcpySize > 0); |
2345 | 27.5k | static_assert(SooSlotMemcpySize <= MaxSooSlotSize()); |
2346 | 27.5k | ABSL_SWISSTABLE_ASSERT(SooSlotMemcpySize <= 2 * slot_size); |
2347 | 27.5k | ABSL_SWISSTABLE_ASSERT(SooSlotMemcpySize >= slot_size); |
2348 | 27.5k | void* next_slot = SlotAddress(target_slot, 1, slot_size); |
2349 | 27.5k | SanitizerUnpoisonMemoryRegion(next_slot, SooSlotMemcpySize - slot_size); |
2350 | 27.5k | std::memcpy(target_slot, common.soo_data(), SooSlotMemcpySize); |
2351 | 27.5k | SanitizerPoisonMemoryRegion(next_slot, SooSlotMemcpySize - slot_size); |
2352 | 27.5k | } else { |
2353 | 0 | static_assert(SooSlotMemcpySize == 0); |
2354 | 0 | policy.transfer_n(&common, target_slot, common.soo_data(), 1); |
2355 | 0 | } |
2356 | 0 | common.set_control(new_ctrl); |
2357 | | |
2358 | | // Full SOO table couldn't be sampled. If SOO table is sampled, it would |
2359 | | // have been resized to the next capacity. |
2360 | 27.5k | ABSL_SWISSTABLE_ASSERT(!common.infoz().IsSampled()); |
2361 | 27.5k | SanitizerUnpoisonMemoryRegion(new_slot, slot_size); |
2362 | 27.5k | return new_slot; |
2363 | 27.5k | } Unexecuted instantiation: void* absl::container_internal::GrowSooTableToNextCapacityAndPrepareInsert<0ul, false>(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, void*, unsigned long (*)(void*, unsigned long), bool) Unexecuted instantiation: void* absl::container_internal::GrowSooTableToNextCapacityAndPrepareInsert<1ul, true>(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, void*, unsigned long (*)(void*, unsigned long), bool) Unexecuted instantiation: void* absl::container_internal::GrowSooTableToNextCapacityAndPrepareInsert<4ul, true>(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, void*, unsigned long (*)(void*, unsigned long), bool) void* absl::container_internal::GrowSooTableToNextCapacityAndPrepareInsert<8ul, true>(absl::container_internal::CommonFields&, absl::container_internal::PolicyFunctions const&, void*, unsigned long (*)(void*, unsigned long), bool) Line | Count | Source | 2297 | 27.5k | void* hasher, HashForKeyFn get_hash, bool force_sampling) { | 2298 | 27.5k | AssertSoo(common, policy); | 2299 | 27.5k | if (ABSL_PREDICT_FALSE(force_sampling)) { | 2300 | | // The table is empty, it is only used for forced sampling of SOO tables. | 2301 | 0 | return GrowEmptySooTableToNextCapacityForceSamplingAndPrepareInsert( | 2302 | 0 | common, policy, hasher, get_hash); | 2303 | 0 | } | 2304 | 27.5k | ABSL_SWISSTABLE_ASSERT(common.size() == policy.soo_capacity()); | 2305 | 27.5k | static constexpr size_t kNewCapacity = NextCapacity(SooCapacity()); | 2306 | | // Generate the seed and compute hashes before `AllocBackingArray` so | 2307 | | // `hasher`, `get_hash`, and `new_ctrl`/`new_slots` do not need to be spilled | 2308 | | // across calls. | 2309 | 27.5k | common.generate_new_seed(/*has_infoz=*/false); | 2310 | 27.5k | const size_t new_hash = get_hash(hasher, common.seed().seed()); | 2311 | 27.5k | const h2_t soo_slot_h2 = H2(policy.hash_slot( | 2312 | 27.5k | policy.hash_fn(common), common.soo_data(), common.seed().seed())); | 2313 | | | 2314 | | // Since the table is not empty, it will not be sampled. | 2315 | | // The decision to sample was already made during the first insertion. | 2316 | | // | 2317 | | // We do not set control and slots in CommonFields yet to avoid overriding | 2318 | | // SOO data. | 2319 | 27.5k | const auto [new_ctrl, new_slots] = | 2320 | 27.5k | AllocBackingArray(common, policy, kNewCapacity, /*has_infoz=*/false, | 2321 | 27.5k | policy.get_char_alloc(common), | 2322 | 27.5k | /*blocked_element_count=*/0); | 2323 | | // Group `common` updates together after `AllocBackingArray` so `inline_data_` | 2324 | | // is modified in a single load-store sequence. | 2325 | 27.5k | common.set_capacity(kNewCapacity); | 2326 | 27.5k | common.InitGrowthLeftNoDeleted(kNewCapacity - 2, kNewCapacity); | 2327 | 27.5k | PrepareInsertCommon(common); | 2328 | 27.5k | ABSL_SWISSTABLE_ASSERT(common.size() == 2); | 2329 | | | 2330 | 27.5k | const size_t offset = Resize1To3NewOffset(new_hash); | 2331 | 27.5k | InitializeThreeElementsControlBytes(soo_slot_h2, H2(new_hash), offset, | 2332 | 27.5k | new_ctrl); | 2333 | | | 2334 | 27.5k | const size_t slot_size = policy.slot_size; | 2335 | 27.5k | SanitizerPoisonMemoryRegion(new_slots, slot_size * kNewCapacity); | 2336 | 27.5k | void* target_slot = SlotAddress(new_slots, SooSlotIndex(), slot_size); | 2337 | 27.5k | void* new_slot = SlotAddress(new_slots, offset, slot_size); | 2338 | 27.5k | SanitizerUnpoisonMemoryRegion(target_slot, slot_size); | 2339 | 27.5k | if constexpr (TransferUsesMemcpy) { | 2340 | | // Target slot is placed at index 1, but capacity is at | 2341 | | // minimum 3. So we are allowed to copy at least twice as much | 2342 | | // memory. | 2343 | 27.5k | static_assert(SooSlotIndex() == 1); | 2344 | 27.5k | static_assert(SooSlotMemcpySize > 0); | 2345 | 27.5k | static_assert(SooSlotMemcpySize <= MaxSooSlotSize()); | 2346 | 27.5k | ABSL_SWISSTABLE_ASSERT(SooSlotMemcpySize <= 2 * slot_size); | 2347 | 27.5k | ABSL_SWISSTABLE_ASSERT(SooSlotMemcpySize >= slot_size); | 2348 | 27.5k | void* next_slot = SlotAddress(target_slot, 1, slot_size); | 2349 | 27.5k | SanitizerUnpoisonMemoryRegion(next_slot, SooSlotMemcpySize - slot_size); | 2350 | 27.5k | std::memcpy(target_slot, common.soo_data(), SooSlotMemcpySize); | 2351 | 27.5k | SanitizerPoisonMemoryRegion(next_slot, SooSlotMemcpySize - slot_size); | 2352 | | } else { | 2353 | | static_assert(SooSlotMemcpySize == 0); | 2354 | | policy.transfer_n(&common, target_slot, common.soo_data(), 1); | 2355 | | } | 2356 | 0 | common.set_control(new_ctrl); | 2357 | | | 2358 | | // Full SOO table couldn't be sampled. If SOO table is sampled, it would | 2359 | | // have been resized to the next capacity. | 2360 | 27.5k | ABSL_SWISSTABLE_ASSERT(!common.infoz().IsSampled()); | 2361 | 27.5k | SanitizerUnpoisonMemoryRegion(new_slot, slot_size); | 2362 | 27.5k | return new_slot; | 2363 | 27.5k | } |
|
2364 | | |
2365 | | void Rehash(CommonFields& common, const PolicyFunctions& __restrict policy, |
2366 | 0 | size_t n) { |
2367 | 0 | const size_t cap = common.capacity(); |
2368 | |
|
2369 | 0 | auto clear_backing_array = [&]() { |
2370 | 0 | ClearBackingArrayNoReuse(common, policy, policy.get_char_alloc(common)); |
2371 | 0 | }; |
2372 | |
|
2373 | 0 | const size_t slot_size = policy.slot_size; |
2374 | |
|
2375 | 0 | if (n == 0) { |
2376 | 0 | if (cap <= policy.soo_capacity()) return; |
2377 | 0 | if (common.empty()) { |
2378 | 0 | clear_backing_array(); |
2379 | 0 | return; |
2380 | 0 | } |
2381 | 0 | if (common.size() <= policy.soo_capacity()) { |
2382 | | // When the table is already sampled, we keep it sampled. |
2383 | 0 | if (common.has_infoz()) { |
2384 | 0 | static constexpr size_t kInitialSampledCapacity = |
2385 | 0 | NextCapacity(SooCapacity()); |
2386 | 0 | if (cap > kInitialSampledCapacity) { |
2387 | 0 | ResizeAllocatedTableWithSeedChange(common, policy, |
2388 | 0 | kInitialSampledCapacity); |
2389 | 0 | } |
2390 | | // This asserts that we didn't lose sampling coverage in `resize`. |
2391 | 0 | ABSL_SWISSTABLE_ASSERT(common.has_infoz()); |
2392 | 0 | return; |
2393 | 0 | } |
2394 | | // Reducing from large capacity to SOO. |
2395 | 0 | ABSL_SWISSTABLE_ASSERT(slot_size <= sizeof(HeapOrSoo)); |
2396 | 0 | ABSL_SWISSTABLE_ASSERT(policy.slot_align_mask < alignof(HeapOrSoo)); |
2397 | 0 | ABSL_SWISSTABLE_ASSERT(!IsSmallCapacity(cap)); |
2398 | 0 | HeapOrSoo tmp_slot; |
2399 | 0 | size_t begin_offset = FindFirstFullSlot(0, cap, common.control()); |
2400 | 0 | policy.transfer_n( |
2401 | 0 | &common, &tmp_slot, |
2402 | 0 | SlotAddress(common.slot_array(cap), begin_offset, slot_size), 1); |
2403 | 0 | clear_backing_array(); |
2404 | 0 | policy.transfer_n(&common, common.soo_data(), &tmp_slot, 1); |
2405 | 0 | common.set_full_soo(); |
2406 | 0 | return; |
2407 | 0 | } |
2408 | 0 | } |
2409 | | |
2410 | | // bitor is a faster way of doing `max` here. We will round up to the next |
2411 | | // power-of-2-minus-1, so bitor is good enough. |
2412 | 0 | const size_t new_capacity = |
2413 | 0 | NormalizeCapacity(n | SizeToCapacity(common.size())); |
2414 | 0 | ValidateMaxCapacity(new_capacity, policy.key_size, policy.slot_size); |
2415 | | // n == 0 unconditionally rehashes as per the standard. |
2416 | 0 | if (n == 0 || new_capacity > cap) { |
2417 | 0 | if (cap == policy.soo_capacity() && common.empty()) { |
2418 | 0 | ResizeEmptyNonAllocatedTableImpl(common, policy, new_capacity, |
2419 | 0 | /*blocked_element_count=*/0, |
2420 | 0 | /*force_infoz=*/false); |
2421 | 0 | } else { |
2422 | 0 | ResizeTableWithSeedChangeImpl(common, policy, new_capacity, |
2423 | 0 | /*force_sample_soo=*/false); |
2424 | 0 | } |
2425 | | // This is after resize, to ensure that we have completed the allocation |
2426 | | // and have potentially sampled the hashtable. |
2427 | 0 | common.infoz().RecordReservation(n); |
2428 | 0 | } |
2429 | 0 | } |
2430 | | |
2431 | | void Copy(CommonFields& common, const PolicyFunctions& __restrict policy, |
2432 | | const CommonFields& other, |
2433 | 0 | void (*copy_fn)(void* set, void* dst, const void* src)) { |
2434 | 0 | other.AssertNotDebugCapacity(); |
2435 | 0 | const size_t size = other.size(); |
2436 | 0 | if (size == 0) return; |
2437 | 0 | const size_t soo_capacity = policy.soo_capacity(); |
2438 | 0 | const size_t slot_size = policy.slot_size; |
2439 | 0 | const bool soo_enabled = policy.soo_enabled; |
2440 | 0 | if (size == 1) { |
2441 | 0 | if (!soo_enabled) { |
2442 | 0 | ReserveEmptyNonAllocatedTableToFitNewSize(common, policy, 1); |
2443 | 0 | common.infoz().RecordStorageChanged(1, 1); |
2444 | 0 | } |
2445 | 0 | IncrementSmallSize(common, policy); |
2446 | 0 | const size_t other_capacity = other.capacity(); |
2447 | 0 | const void* other_slot = |
2448 | 0 | other_capacity <= soo_capacity ? other.soo_data() |
2449 | 0 | : IsSmallCapacity(other_capacity) |
2450 | 0 | ? other.single_non_soo_slot() |
2451 | 0 | : SlotAddress(other.slot_array(other_capacity), |
2452 | 0 | FindFirstFullSlot(0, other_capacity, other.control()), |
2453 | 0 | slot_size); |
2454 | 0 | copy_fn(&common, |
2455 | 0 | soo_enabled ? common.soo_data() |
2456 | 0 | : SingleSlotAddress</*kSooEnabled=*/false>(common), |
2457 | 0 | other_slot); |
2458 | |
|
2459 | 0 | if (soo_enabled && policy.is_hashtablez_enabled && |
2460 | 0 | ShouldSampleNextTable()) { |
2461 | 0 | GrowFullSooTableToNextCapacityForceSampling(common, policy); |
2462 | 0 | } |
2463 | 0 | return; |
2464 | 0 | } |
2465 | | |
2466 | 0 | ReserveTableToFitNewSize(common, policy, size); |
2467 | 0 | const size_t blocked_element_count = common.blocked_element_count(); |
2468 | 0 | auto infoz = common.infoz(); |
2469 | 0 | ABSL_SWISSTABLE_ASSERT(other.capacity() > soo_capacity); |
2470 | 0 | const size_t cap = common.capacity(); |
2471 | 0 | ABSL_SWISSTABLE_ASSERT(cap > soo_capacity); |
2472 | 0 | ABSL_ASSUME(cap > kMaxSmallCapacity); |
2473 | 0 | size_t offset = cap; |
2474 | 0 | const void* hash_fn = policy.hash_fn(common); |
2475 | 0 | auto hasher = policy.hash_slot; |
2476 | 0 | const size_t seed = common.seed().seed(); |
2477 | 0 | void* target_slot_array = common.slot_array(cap); |
2478 | 0 | IterateOverFullSlotsImpl( |
2479 | 0 | other, slot_size, [&](const ctrl_t*, void* that_slot) { |
2480 | | // The table is guaranteed to be empty, so we can do faster than |
2481 | | // a full `insert`. |
2482 | 0 | const size_t hash = (*hasher)(hash_fn, that_slot, seed); |
2483 | 0 | ctrl_t* ctrl = common.control(); |
2484 | 0 | FindInfo target = find_first_non_full(cap, ctrl, hash); |
2485 | 0 | offset = target.offset; |
2486 | 0 | SetCtrl(common, cap, ctrl, offset, H2(hash), slot_size); |
2487 | 0 | infoz.RecordInsertMiss(hash, target.probe_length); |
2488 | 0 | copy_fn(&common, SlotAddress(target_slot_array, offset, slot_size), |
2489 | 0 | that_slot); |
2490 | 0 | common.maybe_increment_generation_on_insert(); |
2491 | 0 | }); |
2492 | 0 | common.increment_size(size); |
2493 | 0 | ResetGrowthLeft(cap, size + blocked_element_count, common); |
2494 | 0 | } |
2495 | | |
2496 | | void ReserveTableToFitNewSize(CommonFields& common, |
2497 | | const PolicyFunctions& __restrict policy, |
2498 | 0 | size_t new_size) { |
2499 | 0 | new_size = |
2500 | 0 | std::min(new_size, MaxValidSize(policy.key_size, policy.slot_size)); |
2501 | 0 | common.reset_reserved_growth(new_size); |
2502 | 0 | common.set_reservation_size(new_size); |
2503 | 0 | ABSL_SWISSTABLE_ASSERT(new_size > policy.soo_capacity()); |
2504 | 0 | const size_t cap = common.capacity(); |
2505 | 0 | if (ABSL_PREDICT_TRUE(common.empty() && cap <= policy.soo_capacity())) { |
2506 | 0 | return ReserveEmptyNonAllocatedTableToFitNewSize(common, policy, new_size); |
2507 | 0 | } |
2508 | | |
2509 | 0 | ABSL_SWISSTABLE_ASSERT(!common.empty() || cap > policy.soo_capacity()); |
2510 | 0 | ABSL_SWISSTABLE_ASSERT(cap > 0); |
2511 | 0 | const size_t max_size_before_growth = |
2512 | 0 | IsSmallCapacity(cap) ? cap |
2513 | 0 | : common.size() + common.GetGrowthLeftTotalSlow(cap); |
2514 | 0 | if (new_size <= max_size_before_growth) { |
2515 | 0 | return; |
2516 | 0 | } |
2517 | 0 | ReserveAllocatedTable(common, policy, new_size); |
2518 | 0 | } |
2519 | | |
2520 | | namespace { |
2521 | | void* PrepareInsertLargeImpl(CommonFields& common, |
2522 | | const PolicyFunctions& __restrict policy, |
2523 | | size_t hash, |
2524 | | Group::NonIterableBitMaskType mask_empty, |
2525 | 12.7M | size_t target_group_offset) { |
2526 | 12.7M | ABSL_SWISSTABLE_ASSERT(!common.is_small()); |
2527 | | // When there are no deleted slots in the table |
2528 | | // and growth_left is positive, we can insert at the first |
2529 | | // empty slot in the probe sequence (target). |
2530 | 12.7M | if (ABSL_PREDICT_FALSE( |
2531 | 12.7M | !common.GetGrowthInfoLowerBound().HasNoDeletedAndGrowthLeft())) { |
2532 | 862k | return PrepareInsertLargeSlow(common, policy, hash); |
2533 | 862k | } |
2534 | 11.8M | PrepareInsertCommon(common); |
2535 | 11.8M | common.OverwriteEmptyAsFull(); |
2536 | 11.8M | const size_t cap = common.capacity(); |
2537 | 11.8M | ABSL_ASSUME(cap > kMaxSmallCapacity); |
2538 | 11.8M | target_group_offset += mask_empty.LowestBitSet(); |
2539 | 11.8M | target_group_offset &= cap; |
2540 | | // Compute before `SetCtrl` to avoid reloading `control_`. |
2541 | 11.8M | void* res = SlotAddress(common.slot_array(cap), target_group_offset, |
2542 | 11.8M | policy.slot_size); |
2543 | 11.8M | SetCtrl(common, cap, common.control(), target_group_offset, H2(hash), |
2544 | 11.8M | policy.slot_size); |
2545 | | // Call NOINLINE function to move infoz instructions out of line. |
2546 | 11.8M | if (common.has_infoz()) { |
2547 | 0 | return ReportInsertMissToInfozAndGetSlot(common, policy, hash, |
2548 | 0 | target_group_offset); |
2549 | 0 | } |
2550 | 11.8M | return res; |
2551 | 11.8M | } |
2552 | | } // namespace |
2553 | | |
2554 | | void* PrepareInsertLarge(CommonFields& common, |
2555 | | const PolicyFunctions& __restrict policy, size_t hash, |
2556 | | Group::NonIterableBitMaskType mask_empty, |
2557 | 12.7M | size_t target_group_offset) { |
2558 | | // NOLINTNEXTLINE(misc-static-assert) |
2559 | 12.7M | ABSL_SWISSTABLE_ASSERT(!SwisstableGenerationsEnabled()); |
2560 | 12.7M | return PrepareInsertLargeImpl(common, policy, hash, mask_empty, |
2561 | 12.7M | target_group_offset); |
2562 | 12.7M | } |
2563 | | |
2564 | | void* PrepareInsertLargeGenerationsEnabled( |
2565 | | CommonFields& common, const PolicyFunctions& __restrict policy, size_t hash, |
2566 | | Group::NonIterableBitMaskType mask_empty, size_t target_group_offset, |
2567 | 0 | absl::FunctionRef<size_t(size_t)> recompute_hash) { |
2568 | | // NOLINTNEXTLINE(misc-static-assert) |
2569 | 0 | ABSL_SWISSTABLE_ASSERT(SwisstableGenerationsEnabled()); |
2570 | 0 | const size_t cap = common.capacity(); |
2571 | 0 | const size_t growth_left = common.GetGrowthLeftTotalSlow(cap); |
2572 | | // As an optimization, we avoid calling ShouldRehashForBugDetection if we |
2573 | | // will end up rehashing anyways. |
2574 | 0 | if (growth_left > 0 && common.should_rehash_for_bug_detection_on_insert()) { |
2575 | | // Move to a different heap allocation in order to detect bugs. |
2576 | 0 | ResizeAllocatedTableWithSeedChange(common, policy, cap); |
2577 | 0 | hash = recompute_hash(common.seed().seed()); |
2578 | 0 | FindInfo target_group; |
2579 | 0 | std::tie(target_group, mask_empty) = |
2580 | 0 | find_first_non_full_group(common, hash); |
2581 | 0 | target_group_offset = target_group.offset; |
2582 | 0 | } |
2583 | 0 | return PrepareInsertLargeImpl(common, policy, hash, mask_empty, |
2584 | 0 | target_group_offset); |
2585 | 0 | } |
2586 | | |
2587 | | namespace { |
2588 | | // Returns true if the following is true |
2589 | | // 1. OptimalMemcpySizeForSooSlotTransfer(left) > |
2590 | | // OptimalMemcpySizeForSooSlotTransfer(left - 1) |
2591 | | // 2. OptimalMemcpySizeForSooSlotTransfer(left) are equal for all i in [left, |
2592 | | // right]. |
2593 | | // This function is used to verify that we have all the possible template |
2594 | | // instantiations for GrowFullSooTableToNextCapacity. |
2595 | | // With this verification the problem may be detected at compile time instead of |
2596 | | // link time. |
2597 | | constexpr bool VerifyOptimalMemcpySizeForSooSlotTransferRange(size_t left, |
2598 | 0 | size_t right) { |
2599 | 0 | size_t optimal_size_for_range = OptimalMemcpySizeForSooSlotTransfer(left); |
2600 | 0 | if (optimal_size_for_range <= OptimalMemcpySizeForSooSlotTransfer(left - 1)) { |
2601 | 0 | return false; |
2602 | 0 | } |
2603 | 0 | for (size_t i = left + 1; i <= right; ++i) { |
2604 | 0 | if (OptimalMemcpySizeForSooSlotTransfer(i) != optimal_size_for_range) { |
2605 | 0 | return false; |
2606 | 0 | } |
2607 | 0 | } |
2608 | 0 | return true; |
2609 | 0 | } |
2610 | | } // namespace |
2611 | | |
2612 | | // We need to instantiate ALL possible template combinations because we define |
2613 | | // the function in the cc file. |
2614 | | template void* GrowSooTableToNextCapacityAndPrepareInsert<0, false>( |
2615 | | CommonFields&, const PolicyFunctions&, void*, HashForKeyFn, bool); |
2616 | | template void* GrowSooTableToNextCapacityAndPrepareInsert< |
2617 | | OptimalMemcpySizeForSooSlotTransfer(1), true>(CommonFields&, |
2618 | | const PolicyFunctions&, void*, |
2619 | | HashForKeyFn, bool); |
2620 | | |
2621 | | static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(2, 3)); |
2622 | | template void* GrowSooTableToNextCapacityAndPrepareInsert< |
2623 | | OptimalMemcpySizeForSooSlotTransfer(3), true>(CommonFields&, |
2624 | | const PolicyFunctions&, void*, |
2625 | | HashForKeyFn, bool); |
2626 | | |
2627 | | #if UINTPTR_MAX == UINT32_MAX |
2628 | | static_assert(MaxSooSlotSize() == 4); |
2629 | | static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(2, 4)); |
2630 | | #else |
2631 | | static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(4, 8)); |
2632 | | template void* GrowSooTableToNextCapacityAndPrepareInsert< |
2633 | | OptimalMemcpySizeForSooSlotTransfer(8), true>(CommonFields&, |
2634 | | const PolicyFunctions&, void*, |
2635 | | HashForKeyFn, bool); |
2636 | | static_assert(MaxSooSlotSize() == 8); |
2637 | | #endif |
2638 | | |
2639 | | template void* AllocateBackingArray<kStandardBackingArrayAlignment, |
2640 | | std::allocator<char>>(void* alloc, |
2641 | | size_t n); |
2642 | | template void DeallocateBackingArray<kStandardBackingArrayAlignment, |
2643 | | std::allocator<char>>(void* alloc, |
2644 | | void* backing_array, |
2645 | | size_t n); |
2646 | | |
2647 | | template void Clear</*kSooEnabled=*/true>(CommonFields& c, |
2648 | | const PolicyFunctions& policy, |
2649 | | DestroySlotFn destroy_slot, |
2650 | | void* alloc); |
2651 | | template void Clear</*kSooEnabled=*/false>(CommonFields& c, |
2652 | | const PolicyFunctions& policy, |
2653 | | DestroySlotFn destroy_slot, |
2654 | | void* alloc); |
2655 | | |
2656 | | template void Destruct</*kSooEnabled=*/true>(CommonFields& c, |
2657 | | const DtorPolicy& policy, |
2658 | | DeallocBackingArrayFn dealloc, |
2659 | | void* alloc); |
2660 | | template void Destruct</*kSooEnabled=*/true>(CommonFields& c, |
2661 | | const DtorPolicy& policy, |
2662 | | DeallocBackingArrayFn dealloc); |
2663 | | template void Destruct</*kSooEnabled=*/true>(CommonFields& c, |
2664 | | const DtorPolicy& policy); |
2665 | | template void Destruct</*kSooEnabled=*/false>(CommonFields& c, |
2666 | | const DtorPolicy& policy, |
2667 | | DeallocBackingArrayFn dealloc, |
2668 | | void* alloc); |
2669 | | template void Destruct</*kSooEnabled=*/false>(CommonFields& c, |
2670 | | const DtorPolicy& policy, |
2671 | | DeallocBackingArrayFn dealloc); |
2672 | | template void Destruct</*kSooEnabled=*/false>(CommonFields& c, |
2673 | | const DtorPolicy& policy); |
2674 | | |
2675 | | // Extern template instantiation for inline functions. |
2676 | | template size_t TryFindNewIndexWithoutProbing(size_t hash, size_t old_index, |
2677 | | size_t old_capacity, |
2678 | | size_t new_capacity); |
2679 | | template void AssertIsFull(const ctrl_t* const& ctrl, const void* slot, |
2680 | | GenerationType generation, |
2681 | | const GenerationType* generation_ptr, |
2682 | | const char* operation); |
2683 | | template void AssertIsValidForComparison(const ctrl_t* const& ctrl, |
2684 | | const void* slot, |
2685 | | GenerationType generation, |
2686 | | const GenerationType* generation_ptr); |
2687 | | template bool AreItersFromSameContainer(const ctrl_t* const& ctrl_a, |
2688 | | const ctrl_t* const& ctrl_b, |
2689 | | const void* slot_a, const void* slot_b); |
2690 | | template void AssertSameContainer(const ctrl_t* const& ctrl_a, |
2691 | | const ctrl_t* const& ctrl_b, |
2692 | | const void* slot_a, const void* slot_b, |
2693 | | const GenerationType* generation_ptr_a, |
2694 | | const GenerationType* generation_ptr_b); |
2695 | | |
2696 | | } // namespace container_internal |
2697 | | ABSL_NAMESPACE_END |
2698 | | } // namespace absl |