/src/abseil-cpp/absl/container/internal/raw_hash_set.cc
Line | Count | Source (jump to first uncovered line) |
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 <atomic> |
18 | | #include <cassert> |
19 | | #include <cstddef> |
20 | | #include <cstring> |
21 | | |
22 | | #include "absl/base/attributes.h" |
23 | | #include "absl/base/config.h" |
24 | | #include "absl/base/dynamic_annotations.h" |
25 | | #include "absl/hash/hash.h" |
26 | | |
27 | | namespace absl { |
28 | | ABSL_NAMESPACE_BEGIN |
29 | | namespace container_internal { |
30 | | |
31 | | // We have space for `growth_left` before a single block of control bytes. A |
32 | | // single block of empty control bytes for tables without any slots allocated. |
33 | | // This enables removing a branch in the hot path of find(). In order to ensure |
34 | | // that the control bytes are aligned to 16, we have 16 bytes before the control |
35 | | // bytes even though growth_left only needs 8. |
36 | 0 | constexpr ctrl_t ZeroCtrlT() { return static_cast<ctrl_t>(0); } |
37 | | alignas(16) ABSL_CONST_INIT ABSL_DLL const ctrl_t kEmptyGroup[32] = { |
38 | | ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), |
39 | | ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), |
40 | | ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), |
41 | | ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), |
42 | | ctrl_t::kSentinel, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, |
43 | | ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, |
44 | | ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, |
45 | | ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty}; |
46 | | |
47 | | #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL |
48 | | constexpr size_t Group::kWidth; |
49 | | #endif |
50 | | |
51 | | namespace { |
52 | | |
53 | | // Returns "random" seed. |
54 | 20 | inline size_t RandomSeed() { |
55 | 20 | #ifdef ABSL_HAVE_THREAD_LOCAL |
56 | 20 | static thread_local size_t counter = 0; |
57 | | // On Linux kernels >= 5.4 the MSAN runtime has a false-positive when |
58 | | // accessing thread local storage data from loaded libraries |
59 | | // (https://github.com/google/sanitizers/issues/1265), for this reason counter |
60 | | // needs to be annotated as initialized. |
61 | 20 | ABSL_ANNOTATE_MEMORY_IS_INITIALIZED(&counter, sizeof(size_t)); |
62 | 20 | size_t value = ++counter; |
63 | | #else // ABSL_HAVE_THREAD_LOCAL |
64 | | static std::atomic<size_t> counter(0); |
65 | | size_t value = counter.fetch_add(1, std::memory_order_relaxed); |
66 | | #endif // ABSL_HAVE_THREAD_LOCAL |
67 | 20 | return value ^ static_cast<size_t>(reinterpret_cast<uintptr_t>(&counter)); |
68 | 20 | } |
69 | | |
70 | | } // namespace |
71 | | |
72 | 0 | GenerationType* EmptyGeneration() { |
73 | 0 | if (SwisstableGenerationsEnabled()) { |
74 | 0 | constexpr size_t kNumEmptyGenerations = 1024; |
75 | 0 | static constexpr GenerationType kEmptyGenerations[kNumEmptyGenerations]{}; |
76 | 0 | return const_cast<GenerationType*>( |
77 | 0 | &kEmptyGenerations[RandomSeed() % kNumEmptyGenerations]); |
78 | 0 | } |
79 | 0 | return nullptr; |
80 | 0 | } |
81 | | |
82 | | bool CommonFieldsGenerationInfoEnabled:: |
83 | | should_rehash_for_bug_detection_on_insert(const ctrl_t* ctrl, |
84 | 0 | size_t capacity) const { |
85 | 0 | if (reserved_growth_ == kReservedGrowthJustRanOut) return true; |
86 | 0 | if (reserved_growth_ > 0) return false; |
87 | | // Note: we can't use the abseil-random library because abseil-random |
88 | | // depends on swisstable. We want to return true with probability |
89 | | // `min(1, RehashProbabilityConstant() / capacity())`. In order to do this, |
90 | | // we probe based on a random hash and see if the offset is less than |
91 | | // RehashProbabilityConstant(). |
92 | 0 | return probe(ctrl, capacity, absl::HashOf(RandomSeed())).offset() < |
93 | 0 | RehashProbabilityConstant(); |
94 | 0 | } |
95 | | |
96 | 20 | bool ShouldInsertBackwards(size_t hash, const ctrl_t* ctrl) { |
97 | | // To avoid problems with weak hashes and single bit tests, we use % 13. |
98 | | // TODO(kfm,sbenza): revisit after we do unconditional mixing |
99 | 20 | return (H1(hash, ctrl) ^ RandomSeed()) % 13 > 6; |
100 | 20 | } |
101 | | |
102 | 0 | void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity) { |
103 | 0 | assert(ctrl[capacity] == ctrl_t::kSentinel); |
104 | 0 | assert(IsValidCapacity(capacity)); |
105 | 0 | for (ctrl_t* pos = ctrl; pos < ctrl + capacity; pos += Group::kWidth) { |
106 | 0 | Group{pos}.ConvertSpecialToEmptyAndFullToDeleted(pos); |
107 | 0 | } |
108 | | // Copy the cloned ctrl bytes. |
109 | 0 | std::memcpy(ctrl + capacity + 1, ctrl, NumClonedBytes()); |
110 | 0 | ctrl[capacity] = ctrl_t::kSentinel; |
111 | 0 | } |
112 | | // Extern template instantiation for inline function. |
113 | | template FindInfo find_first_non_full(const CommonFields&, size_t); |
114 | | |
115 | | FindInfo find_first_non_full_outofline(const CommonFields& common, |
116 | 0 | size_t hash) { |
117 | 0 | return find_first_non_full(common, hash); |
118 | 0 | } |
119 | | |
120 | | // Returns the address of the ith slot in slots where each slot occupies |
121 | | // slot_size. |
122 | | static inline void* SlotAddress(void* slot_array, size_t slot, |
123 | 0 | size_t slot_size) { |
124 | 0 | return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot_array) + |
125 | 0 | (slot * slot_size)); |
126 | 0 | } |
127 | | |
128 | | // Returns the address of the slot just after slot assuming each slot has the |
129 | | // specified size. |
130 | 0 | static inline void* NextSlot(void* slot, size_t slot_size) { |
131 | 0 | return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) + slot_size); |
132 | 0 | } |
133 | | |
134 | | // Returns the address of the slot just before slot assuming each slot has the |
135 | | // specified size. |
136 | 0 | static inline void* PrevSlot(void* slot, size_t slot_size) { |
137 | 0 | return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size); |
138 | 0 | } |
139 | | |
140 | | void DropDeletesWithoutResize(CommonFields& common, |
141 | 0 | const PolicyFunctions& policy, void* tmp_space) { |
142 | 0 | void* set = &common; |
143 | 0 | void* slot_array = common.slot_array(); |
144 | 0 | const size_t capacity = common.capacity(); |
145 | 0 | assert(IsValidCapacity(capacity)); |
146 | 0 | assert(!is_small(capacity)); |
147 | | // Algorithm: |
148 | | // - mark all DELETED slots as EMPTY |
149 | | // - mark all FULL slots as DELETED |
150 | | // - for each slot marked as DELETED |
151 | | // hash = Hash(element) |
152 | | // target = find_first_non_full(hash) |
153 | | // if target is in the same group |
154 | | // mark slot as FULL |
155 | | // else if target is EMPTY |
156 | | // transfer element to target |
157 | | // mark slot as EMPTY |
158 | | // mark target as FULL |
159 | | // else if target is DELETED |
160 | | // swap current element with target element |
161 | | // mark target as FULL |
162 | | // repeat procedure for current slot with moved from element (target) |
163 | 0 | ctrl_t* ctrl = common.control(); |
164 | 0 | ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity); |
165 | 0 | auto hasher = policy.hash_slot; |
166 | 0 | auto transfer = policy.transfer; |
167 | 0 | const size_t slot_size = policy.slot_size; |
168 | |
|
169 | 0 | size_t total_probe_length = 0; |
170 | 0 | void* slot_ptr = SlotAddress(slot_array, 0, slot_size); |
171 | 0 | for (size_t i = 0; i != capacity; |
172 | 0 | ++i, slot_ptr = NextSlot(slot_ptr, slot_size)) { |
173 | 0 | assert(slot_ptr == SlotAddress(slot_array, i, slot_size)); |
174 | 0 | if (!IsDeleted(ctrl[i])) continue; |
175 | 0 | const size_t hash = (*hasher)(set, slot_ptr); |
176 | 0 | const FindInfo target = find_first_non_full(common, hash); |
177 | 0 | const size_t new_i = target.offset; |
178 | 0 | total_probe_length += target.probe_length; |
179 | | |
180 | | // Verify if the old and new i fall within the same group wrt the hash. |
181 | | // If they do, we don't need to move the object as it falls already in the |
182 | | // best probe we can. |
183 | 0 | const size_t probe_offset = probe(common, hash).offset(); |
184 | 0 | const auto probe_index = [probe_offset, capacity](size_t pos) { |
185 | 0 | return ((pos - probe_offset) & capacity) / Group::kWidth; |
186 | 0 | }; |
187 | | |
188 | | // Element doesn't move. |
189 | 0 | if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) { |
190 | 0 | SetCtrl(common, i, H2(hash), slot_size); |
191 | 0 | continue; |
192 | 0 | } |
193 | | |
194 | 0 | void* new_slot_ptr = SlotAddress(slot_array, new_i, slot_size); |
195 | 0 | if (IsEmpty(ctrl[new_i])) { |
196 | | // Transfer element to the empty spot. |
197 | | // SetCtrl poisons/unpoisons the slots so we have to call it at the |
198 | | // right time. |
199 | 0 | SetCtrl(common, new_i, H2(hash), slot_size); |
200 | 0 | (*transfer)(set, new_slot_ptr, slot_ptr); |
201 | 0 | SetCtrl(common, i, ctrl_t::kEmpty, slot_size); |
202 | 0 | } else { |
203 | 0 | assert(IsDeleted(ctrl[new_i])); |
204 | 0 | SetCtrl(common, new_i, H2(hash), slot_size); |
205 | | // Until we are done rehashing, DELETED marks previously FULL slots. |
206 | | |
207 | | // Swap i and new_i elements. |
208 | 0 | (*transfer)(set, tmp_space, new_slot_ptr); |
209 | 0 | (*transfer)(set, new_slot_ptr, slot_ptr); |
210 | 0 | (*transfer)(set, slot_ptr, tmp_space); |
211 | | |
212 | | // repeat the processing of the ith slot |
213 | 0 | --i; |
214 | 0 | slot_ptr = PrevSlot(slot_ptr, slot_size); |
215 | 0 | } |
216 | 0 | } |
217 | 0 | ResetGrowthLeft(common); |
218 | 0 | common.infoz().RecordRehash(total_probe_length); |
219 | 0 | } |
220 | | |
221 | 0 | void EraseMetaOnly(CommonFields& c, ctrl_t* it, size_t slot_size) { |
222 | 0 | assert(IsFull(*it) && "erasing a dangling iterator"); |
223 | 0 | c.decrement_size(); |
224 | 0 | const auto index = static_cast<size_t>(it - c.control()); |
225 | 0 | const size_t index_before = (index - Group::kWidth) & c.capacity(); |
226 | 0 | const auto empty_after = Group(it).MaskEmpty(); |
227 | 0 | const auto empty_before = Group(c.control() + index_before).MaskEmpty(); |
228 | | |
229 | | // We count how many consecutive non empties we have to the right and to the |
230 | | // left of `it`. If the sum is >= kWidth then there is at least one probe |
231 | | // window that might have seen a full group. |
232 | 0 | bool was_never_full = empty_before && empty_after && |
233 | 0 | static_cast<size_t>(empty_after.TrailingZeros()) + |
234 | 0 | empty_before.LeadingZeros() < |
235 | 0 | Group::kWidth; |
236 | |
|
237 | 0 | SetCtrl(c, index, was_never_full ? ctrl_t::kEmpty : ctrl_t::kDeleted, |
238 | 0 | slot_size); |
239 | 0 | c.set_growth_left(c.growth_left() + (was_never_full ? 1 : 0)); |
240 | 0 | c.infoz().RecordErase(); |
241 | 0 | } |
242 | | |
243 | | void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy, |
244 | 0 | bool reuse) { |
245 | 0 | c.set_size(0); |
246 | 0 | if (reuse) { |
247 | 0 | ResetCtrl(c, policy.slot_size); |
248 | 0 | c.infoz().RecordStorageChanged(0, c.capacity()); |
249 | 0 | } else { |
250 | | // We need to record infoz before calling dealloc, which will unregister |
251 | | // infoz. |
252 | 0 | c.infoz().RecordClearedReservation(); |
253 | 0 | c.infoz().RecordStorageChanged(0, 0); |
254 | 0 | (*policy.dealloc)(c, policy); |
255 | 0 | c.set_control(EmptyGroup()); |
256 | 0 | c.set_generation_ptr(EmptyGeneration()); |
257 | 0 | c.set_slots(nullptr); |
258 | 0 | c.set_capacity(0); |
259 | 0 | } |
260 | 0 | } |
261 | | |
262 | | } // namespace container_internal |
263 | | ABSL_NAMESPACE_END |
264 | | } // namespace absl |