raw_hash_set.cc (53658B)
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 <cstdint> 21 #include <cstring> 22 23 #include "absl/base/attributes.h" 24 #include "absl/base/config.h" 25 #include "absl/base/dynamic_annotations.h" 26 #include "absl/base/internal/endian.h" 27 #include "absl/base/internal/raw_logging.h" 28 #include "absl/base/optimization.h" 29 #include "absl/container/internal/container_memory.h" 30 #include "absl/container/internal/hashtable_control_bytes.h" 31 #include "absl/container/internal/hashtablez_sampler.h" 32 #include "absl/functional/function_ref.h" 33 #include "absl/hash/hash.h" 34 #include "absl/numeric/bits.h" 35 36 namespace absl { 37 ABSL_NAMESPACE_BEGIN 38 namespace container_internal { 39 40 // Represents a control byte corresponding to a full slot with arbitrary hash. 41 constexpr ctrl_t ZeroCtrlT() { return static_cast<ctrl_t>(0); } 42 43 // We have space for `growth_info` before a single block of control bytes. A 44 // single block of empty control bytes for tables without any slots allocated. 45 // This enables removing a branch in the hot path of find(). In order to ensure 46 // that the control bytes are aligned to 16, we have 16 bytes before the control 47 // bytes even though growth_info only needs 8. 48 alignas(16) ABSL_CONST_INIT ABSL_DLL const ctrl_t kEmptyGroup[32] = { 49 ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), 50 ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), 51 ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), 52 ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), 53 ctrl_t::kSentinel, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, 54 ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, 55 ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, 56 ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty}; 57 58 // We need one full byte followed by a sentinel byte for iterator::operator++ to 59 // work. We have a full group after kSentinel to be safe (in case operator++ is 60 // changed to read a full group). 61 ABSL_CONST_INIT ABSL_DLL const ctrl_t kSooControl[17] = { 62 ZeroCtrlT(), ctrl_t::kSentinel, ZeroCtrlT(), ctrl_t::kEmpty, 63 ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, 64 ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, 65 ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, 66 ctrl_t::kEmpty}; 67 static_assert(NumControlBytes(SooCapacity()) <= 17, 68 "kSooControl capacity too small"); 69 70 namespace { 71 72 [[noreturn]] ABSL_ATTRIBUTE_NOINLINE void HashTableSizeOverflow() { 73 ABSL_RAW_LOG(FATAL, "Hash table size overflow"); 74 } 75 76 void ValidateMaxSize(size_t size, size_t slot_size) { 77 if (IsAboveValidSize(size, slot_size)) { 78 HashTableSizeOverflow(); 79 } 80 } 81 82 // Returns "random" seed. 83 inline size_t RandomSeed() { 84 #ifdef ABSL_HAVE_THREAD_LOCAL 85 static thread_local size_t counter = 0; 86 size_t value = ++counter; 87 #else // ABSL_HAVE_THREAD_LOCAL 88 static std::atomic<size_t> counter(0); 89 size_t value = counter.fetch_add(1, std::memory_order_relaxed); 90 #endif // ABSL_HAVE_THREAD_LOCAL 91 return value ^ static_cast<size_t>(reinterpret_cast<uintptr_t>(&counter)); 92 } 93 94 bool ShouldRehashForBugDetection(PerTableSeed seed, size_t capacity) { 95 // Note: we can't use the abseil-random library because abseil-random 96 // depends on swisstable. We want to return true with probability 97 // `min(1, RehashProbabilityConstant() / capacity())`. In order to do this, 98 // we probe based on a random hash and see if the offset is less than 99 // RehashProbabilityConstant(). 100 return probe(seed, capacity, absl::HashOf(RandomSeed())).offset() < 101 RehashProbabilityConstant(); 102 } 103 104 // Find a non-deterministic hash for single group table. 105 // Last two bits are used to find a position for a newly inserted element after 106 // resize. 107 // This function is mixing all bits of hash and seed to maximize entropy. 108 size_t SingleGroupTableH1(size_t hash, PerTableSeed seed) { 109 return static_cast<size_t>(absl::popcount(hash ^ seed.seed())); 110 } 111 112 // Returns the address of the slot `i` iterations after `slot` assuming each 113 // slot has the specified size. 114 inline void* NextSlot(void* slot, size_t slot_size, size_t i = 1) { 115 return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) + 116 slot_size * i); 117 } 118 119 // Returns the address of the slot just before `slot` assuming each slot has the 120 // specified size. 121 inline void* PrevSlot(void* slot, size_t slot_size) { 122 return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size); 123 } 124 125 } // namespace 126 127 GenerationType* EmptyGeneration() { 128 if (SwisstableGenerationsEnabled()) { 129 constexpr size_t kNumEmptyGenerations = 1024; 130 static constexpr GenerationType kEmptyGenerations[kNumEmptyGenerations]{}; 131 return const_cast<GenerationType*>( 132 &kEmptyGenerations[RandomSeed() % kNumEmptyGenerations]); 133 } 134 return nullptr; 135 } 136 137 bool CommonFieldsGenerationInfoEnabled:: 138 should_rehash_for_bug_detection_on_insert(PerTableSeed seed, 139 size_t capacity) const { 140 if (reserved_growth_ == kReservedGrowthJustRanOut) return true; 141 if (reserved_growth_ > 0) return false; 142 return ShouldRehashForBugDetection(seed, capacity); 143 } 144 145 bool CommonFieldsGenerationInfoEnabled::should_rehash_for_bug_detection_on_move( 146 PerTableSeed seed, size_t capacity) const { 147 return ShouldRehashForBugDetection(seed, capacity); 148 } 149 150 bool ShouldInsertBackwardsForDebug(size_t capacity, size_t hash, 151 PerTableSeed seed) { 152 // To avoid problems with weak hashes and single bit tests, we use % 13. 153 // TODO(kfm,sbenza): revisit after we do unconditional mixing 154 return !is_small(capacity) && (H1(hash, seed) ^ RandomSeed()) % 13 > 6; 155 } 156 157 void IterateOverFullSlots(const CommonFields& c, size_t slot_size, 158 absl::FunctionRef<void(const ctrl_t*, void*)> cb) { 159 const size_t cap = c.capacity(); 160 const ctrl_t* ctrl = c.control(); 161 void* slot = c.slot_array(); 162 if (is_small(cap)) { 163 // Mirrored/cloned control bytes in small table are also located in the 164 // first group (starting from position 0). We are taking group from position 165 // `capacity` in order to avoid duplicates. 166 167 // Small tables capacity fits into portable group, where 168 // GroupPortableImpl::MaskFull is more efficient for the 169 // capacity <= GroupPortableImpl::kWidth. 170 assert(cap <= GroupPortableImpl::kWidth && 171 "unexpectedly large small capacity"); 172 static_assert(Group::kWidth >= GroupPortableImpl::kWidth, 173 "unexpected group width"); 174 // Group starts from kSentinel slot, so indices in the mask will 175 // be increased by 1. 176 const auto mask = GroupPortableImpl(ctrl + cap).MaskFull(); 177 --ctrl; 178 slot = PrevSlot(slot, slot_size); 179 for (uint32_t i : mask) { 180 cb(ctrl + i, SlotAddress(slot, i, slot_size)); 181 } 182 return; 183 } 184 size_t remaining = c.size(); 185 ABSL_ATTRIBUTE_UNUSED const size_t original_size_for_assert = remaining; 186 while (remaining != 0) { 187 for (uint32_t i : GroupFullEmptyOrDeleted(ctrl).MaskFull()) { 188 assert(IsFull(ctrl[i]) && "hash table was modified unexpectedly"); 189 cb(ctrl + i, SlotAddress(slot, i, slot_size)); 190 --remaining; 191 } 192 ctrl += Group::kWidth; 193 slot = NextSlot(slot, slot_size, Group::kWidth); 194 assert((remaining == 0 || *(ctrl - 1) != ctrl_t::kSentinel) && 195 "hash table was modified unexpectedly"); 196 } 197 // NOTE: erasure of the current element is allowed in callback for 198 // absl::erase_if specialization. So we use `>=`. 199 assert(original_size_for_assert >= c.size() && 200 "hash table was modified unexpectedly"); 201 } 202 203 size_t PrepareInsertAfterSoo(size_t hash, size_t slot_size, 204 CommonFields& common) { 205 assert(common.capacity() == NextCapacity(SooCapacity())); 206 // After resize from capacity 1 to 3, we always have exactly the slot with 207 // index 1 occupied, so we need to insert either at index 0 or index 2. 208 static_assert(SooSlotIndex() == 1, ""); 209 PrepareInsertCommon(common); 210 const size_t offset = SingleGroupTableH1(hash, common.seed()) & 2; 211 common.growth_info().OverwriteEmptyAsFull(); 212 SetCtrlInSingleGroupTable(common, offset, H2(hash), slot_size); 213 common.infoz().RecordInsert(hash, /*distance_from_desired=*/0); 214 return offset; 215 } 216 217 void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity) { 218 assert(ctrl[capacity] == ctrl_t::kSentinel); 219 assert(IsValidCapacity(capacity)); 220 for (ctrl_t* pos = ctrl; pos < ctrl + capacity; pos += Group::kWidth) { 221 Group{pos}.ConvertSpecialToEmptyAndFullToDeleted(pos); 222 } 223 // Copy the cloned ctrl bytes. 224 std::memcpy(ctrl + capacity + 1, ctrl, NumClonedBytes()); 225 ctrl[capacity] = ctrl_t::kSentinel; 226 } 227 // Extern template instantiation for inline function. 228 template FindInfo find_first_non_full(const CommonFields&, size_t); 229 230 FindInfo find_first_non_full_outofline(const CommonFields& common, 231 size_t hash) { 232 return find_first_non_full(common, hash); 233 } 234 235 namespace { 236 237 void ResetGrowthLeft(CommonFields& common) { 238 common.growth_info().InitGrowthLeftNoDeleted( 239 CapacityToGrowth(common.capacity()) - common.size()); 240 } 241 242 // Finds guaranteed to exists empty slot from the given position. 243 // NOTE: this function is almost never triggered inside of the 244 // DropDeletesWithoutResize, so we keep it simple. 245 // The table is rather sparse, so empty slot will be found very quickly. 246 size_t FindEmptySlot(size_t start, size_t end, const ctrl_t* ctrl) { 247 for (size_t i = start; i < end; ++i) { 248 if (IsEmpty(ctrl[i])) { 249 return i; 250 } 251 } 252 ABSL_UNREACHABLE(); 253 } 254 255 // Finds guaranteed to exist full slot starting from the given position. 256 // NOTE: this function is only triggered for rehash(0), when we need to 257 // go back to SOO state, so we keep it simple. 258 size_t FindFirstFullSlot(size_t start, size_t end, const ctrl_t* ctrl) { 259 for (size_t i = start; i < end; ++i) { 260 if (IsFull(ctrl[i])) { 261 return i; 262 } 263 } 264 ABSL_UNREACHABLE(); 265 } 266 267 size_t DropDeletesWithoutResizeAndPrepareInsert(CommonFields& common, 268 const PolicyFunctions& policy, 269 size_t new_hash) { 270 void* set = &common; 271 void* slot_array = common.slot_array(); 272 const size_t capacity = common.capacity(); 273 assert(IsValidCapacity(capacity)); 274 assert(!is_small(capacity)); 275 // Algorithm: 276 // - mark all DELETED slots as EMPTY 277 // - mark all FULL slots as DELETED 278 // - for each slot marked as DELETED 279 // hash = Hash(element) 280 // target = find_first_non_full(hash) 281 // if target is in the same group 282 // mark slot as FULL 283 // else if target is EMPTY 284 // transfer element to target 285 // mark slot as EMPTY 286 // mark target as FULL 287 // else if target is DELETED 288 // swap current element with target element 289 // mark target as FULL 290 // repeat procedure for current slot with moved from element (target) 291 ctrl_t* ctrl = common.control(); 292 ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity); 293 const void* hash_fn = policy.hash_fn(common); 294 auto hasher = policy.hash_slot; 295 auto transfer_n = policy.transfer_n; 296 const size_t slot_size = policy.slot_size; 297 298 size_t total_probe_length = 0; 299 void* slot_ptr = SlotAddress(slot_array, 0, slot_size); 300 301 // The index of an empty slot that can be used as temporary memory for 302 // the swap operation. 303 constexpr size_t kUnknownId = ~size_t{}; 304 size_t tmp_space_id = kUnknownId; 305 306 for (size_t i = 0; i != capacity; 307 ++i, slot_ptr = NextSlot(slot_ptr, slot_size)) { 308 assert(slot_ptr == SlotAddress(slot_array, i, slot_size)); 309 if (IsEmpty(ctrl[i])) { 310 tmp_space_id = i; 311 continue; 312 } 313 if (!IsDeleted(ctrl[i])) continue; 314 const size_t hash = (*hasher)(hash_fn, slot_ptr); 315 const FindInfo target = find_first_non_full(common, hash); 316 const size_t new_i = target.offset; 317 total_probe_length += target.probe_length; 318 319 // Verify if the old and new i fall within the same group wrt the hash. 320 // If they do, we don't need to move the object as it falls already in the 321 // best probe we can. 322 const size_t probe_offset = probe(common, hash).offset(); 323 const h2_t h2 = H2(hash); 324 const auto probe_index = [probe_offset, capacity](size_t pos) { 325 return ((pos - probe_offset) & capacity) / Group::kWidth; 326 }; 327 328 // Element doesn't move. 329 if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) { 330 SetCtrlInLargeTable(common, i, h2, slot_size); 331 continue; 332 } 333 334 void* new_slot_ptr = SlotAddress(slot_array, new_i, slot_size); 335 if (IsEmpty(ctrl[new_i])) { 336 // Transfer element to the empty spot. 337 // SetCtrl poisons/unpoisons the slots so we have to call it at the 338 // right time. 339 SetCtrlInLargeTable(common, new_i, h2, slot_size); 340 (*transfer_n)(set, new_slot_ptr, slot_ptr, 1); 341 SetCtrlInLargeTable(common, i, ctrl_t::kEmpty, slot_size); 342 // Initialize or change empty space id. 343 tmp_space_id = i; 344 } else { 345 assert(IsDeleted(ctrl[new_i])); 346 SetCtrlInLargeTable(common, new_i, h2, slot_size); 347 // Until we are done rehashing, DELETED marks previously FULL slots. 348 349 if (tmp_space_id == kUnknownId) { 350 tmp_space_id = FindEmptySlot(i + 1, capacity, ctrl); 351 } 352 void* tmp_space = SlotAddress(slot_array, tmp_space_id, slot_size); 353 SanitizerUnpoisonMemoryRegion(tmp_space, slot_size); 354 355 // Swap i and new_i elements. 356 (*transfer_n)(set, tmp_space, new_slot_ptr, 1); 357 (*transfer_n)(set, new_slot_ptr, slot_ptr, 1); 358 (*transfer_n)(set, slot_ptr, tmp_space, 1); 359 360 SanitizerPoisonMemoryRegion(tmp_space, slot_size); 361 362 // repeat the processing of the ith slot 363 --i; 364 slot_ptr = PrevSlot(slot_ptr, slot_size); 365 } 366 } 367 // Prepare insert for the new element. 368 PrepareInsertCommon(common); 369 ResetGrowthLeft(common); 370 FindInfo find_info = find_first_non_full(common, new_hash); 371 SetCtrlInLargeTable(common, find_info.offset, H2(new_hash), policy.slot_size); 372 common.infoz().RecordInsert(new_hash, find_info.probe_length); 373 common.infoz().RecordRehash(total_probe_length); 374 return find_info.offset; 375 } 376 377 static bool WasNeverFull(CommonFields& c, size_t index) { 378 if (is_single_group(c.capacity())) { 379 return true; 380 } 381 const size_t index_before = (index - Group::kWidth) & c.capacity(); 382 const auto empty_after = Group(c.control() + index).MaskEmpty(); 383 const auto empty_before = Group(c.control() + index_before).MaskEmpty(); 384 385 // We count how many consecutive non empties we have to the right and to the 386 // left of `it`. If the sum is >= kWidth then there is at least one probe 387 // window that might have seen a full group. 388 return empty_before && empty_after && 389 static_cast<size_t>(empty_after.TrailingZeros()) + 390 empty_before.LeadingZeros() < 391 Group::kWidth; 392 } 393 394 // Updates the control bytes to indicate a completely empty table such that all 395 // control bytes are kEmpty except for the kSentinel byte. 396 void ResetCtrl(CommonFields& common, size_t slot_size) { 397 const size_t capacity = common.capacity(); 398 ctrl_t* ctrl = common.control(); 399 static constexpr size_t kTwoGroupCapacity = 2 * Group::kWidth - 1; 400 if (ABSL_PREDICT_TRUE(capacity <= kTwoGroupCapacity)) { 401 std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty), Group::kWidth); 402 std::memset(ctrl + capacity, static_cast<int8_t>(ctrl_t::kEmpty), 403 Group::kWidth); 404 if (capacity == kTwoGroupCapacity) { 405 std::memset(ctrl + Group::kWidth, static_cast<int8_t>(ctrl_t::kEmpty), 406 Group::kWidth); 407 } 408 } else { 409 std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty), 410 capacity + 1 + NumClonedBytes()); 411 } 412 ctrl[capacity] = ctrl_t::kSentinel; 413 SanitizerPoisonMemoryRegion(common.slot_array(), slot_size * capacity); 414 } 415 416 // Initializes control bytes for single element table. 417 // Capacity of the table must be 1. 418 ABSL_ATTRIBUTE_ALWAYS_INLINE inline void InitializeSingleElementControlBytes( 419 uint64_t h2, ctrl_t* new_ctrl) { 420 static constexpr uint64_t kEmptyXorSentinel = 421 static_cast<uint8_t>(ctrl_t::kEmpty) ^ 422 static_cast<uint8_t>(ctrl_t::kSentinel); 423 static constexpr uint64_t kEmpty64 = static_cast<uint8_t>(ctrl_t::kEmpty); 424 // The first 8 bytes, where present slot positions are replaced with 0. 425 static constexpr uint64_t kFirstCtrlBytesWithZeroes = 426 k8EmptyBytes ^ kEmpty64 ^ (kEmptyXorSentinel << 8) ^ (kEmpty64 << 16); 427 428 // Fill the original 0th and mirrored 2nd bytes with the hash. 429 // Result will look like: 430 // HSHEEEEE 431 // Where H = h2, E = kEmpty, S = kSentinel. 432 const uint64_t first_ctrl_bytes = 433 (h2 | kFirstCtrlBytesWithZeroes) | (h2 << 16); 434 // Fill last bytes with kEmpty. 435 std::memset(new_ctrl + 1, static_cast<int8_t>(ctrl_t::kEmpty), Group::kWidth); 436 // Overwrite the first 3 bytes with HSH. Other bytes will not be changed. 437 absl::little_endian::Store64(new_ctrl, first_ctrl_bytes); 438 } 439 440 // Initializes control bytes after SOO to the next capacity. 441 // The table must be non-empty SOO. 442 ABSL_ATTRIBUTE_ALWAYS_INLINE inline void 443 InitializeThreeElementsControlBytesAfterSoo(size_t hash, ctrl_t* new_ctrl) { 444 static constexpr size_t kNewCapacity = NextCapacity(SooCapacity()); 445 static_assert(kNewCapacity == 3); 446 static_assert(is_single_group(kNewCapacity)); 447 static_assert(SooSlotIndex() == 1); 448 449 static constexpr uint64_t kEmptyXorSentinel = 450 static_cast<uint8_t>(ctrl_t::kEmpty) ^ 451 static_cast<uint8_t>(ctrl_t::kSentinel); 452 static constexpr uint64_t kEmpty64 = static_cast<uint8_t>(ctrl_t::kEmpty); 453 static constexpr size_t kMirroredSooSlotIndex = 454 SooSlotIndex() + kNewCapacity + 1; 455 // The first 8 bytes, where present slot positions are replaced with 0. 456 static constexpr uint64_t kFirstCtrlBytesWithZeroes = 457 k8EmptyBytes ^ (kEmpty64 << (8 * SooSlotIndex())) ^ 458 (kEmptyXorSentinel << (8 * kNewCapacity)) ^ 459 (kEmpty64 << (8 * kMirroredSooSlotIndex)); 460 461 const uint64_t h2 = static_cast<uint64_t>(H2(hash)); 462 // Fill the original 0th and mirrored 2nd bytes with the hash. 463 // Result will look like: 464 // EHESEHEE 465 // Where H = h2, E = kEmpty, S = kSentinel. 466 const uint64_t first_ctrl_bytes = 467 ((h2 << (8 * SooSlotIndex())) | kFirstCtrlBytesWithZeroes) | 468 (h2 << (8 * kMirroredSooSlotIndex)); 469 470 // Fill last bytes with kEmpty. 471 std::memset(new_ctrl + kNewCapacity, static_cast<int8_t>(ctrl_t::kEmpty), 472 Group::kWidth); 473 // Overwrite the first 8 bytes with first_ctrl_bytes. 474 absl::little_endian::Store64(new_ctrl, first_ctrl_bytes); 475 476 // Example for group size 16: 477 // new_ctrl after 1st memset = ???EEEEEEEEEEEEEEEE 478 // new_ctrl after 2nd store = EHESEHEEEEEEEEEEEEE 479 480 // Example for group size 8: 481 // new_ctrl after 1st memset = ???EEEEEEEE 482 // new_ctrl after 2nd store = EHESEHEEEEE 483 } 484 485 } // namespace 486 487 void EraseMetaOnly(CommonFields& c, size_t index, size_t slot_size) { 488 assert(IsFull(c.control()[index]) && "erasing a dangling iterator"); 489 c.decrement_size(); 490 c.infoz().RecordErase(); 491 492 if (WasNeverFull(c, index)) { 493 SetCtrl(c, index, ctrl_t::kEmpty, slot_size); 494 c.growth_info().OverwriteFullAsEmpty(); 495 return; 496 } 497 498 c.growth_info().OverwriteFullAsDeleted(); 499 SetCtrlInLargeTable(c, index, ctrl_t::kDeleted, slot_size); 500 } 501 502 void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy, 503 void* alloc, bool reuse, bool soo_enabled) { 504 if (reuse) { 505 c.set_size_to_zero(); 506 assert(!soo_enabled || c.capacity() > SooCapacity()); 507 ResetCtrl(c, policy.slot_size); 508 ResetGrowthLeft(c); 509 c.infoz().RecordStorageChanged(0, c.capacity()); 510 } else { 511 // We need to record infoz before calling dealloc, which will unregister 512 // infoz. 513 c.infoz().RecordClearedReservation(); 514 c.infoz().RecordStorageChanged(0, soo_enabled ? SooCapacity() : 0); 515 c.infoz().Unregister(); 516 (*policy.dealloc)(alloc, c.capacity(), c.control(), policy.slot_size, 517 policy.slot_align, c.has_infoz()); 518 c = soo_enabled ? CommonFields{soo_tag_t{}} : CommonFields{non_soo_tag_t{}}; 519 } 520 } 521 522 namespace { 523 524 // Poisons empty slots. It is useful when slots are transferred via memcpy. 525 // PRECONDITIONs: common.control() is fully initialized. 526 void PoisonEmptySlots(CommonFields& c, size_t slot_size) { 527 for (size_t i = 0; i < c.capacity(); ++i) { 528 if (!IsFull(c.control()[i])) { 529 SanitizerPoisonMemoryRegion(SlotAddress(c.slot_array(), i, slot_size), 530 slot_size); 531 } 532 } 533 } 534 535 enum class ResizeNonSooMode { 536 kGuaranteedEmpty, 537 kGuaranteedAllocated, 538 }; 539 540 template <ResizeNonSooMode kMode> 541 void ResizeNonSooImpl(CommonFields& common, const PolicyFunctions& policy, 542 size_t new_capacity, HashtablezInfoHandle infoz) { 543 assert(IsValidCapacity(new_capacity)); 544 assert(new_capacity > policy.soo_capacity); 545 546 const size_t old_capacity = common.capacity(); 547 [[maybe_unused]] ctrl_t* old_ctrl = common.control(); 548 [[maybe_unused]] void* old_slots = common.slot_array(); 549 550 const size_t slot_size = policy.slot_size; 551 const size_t slot_align = policy.slot_align; 552 const bool has_infoz = infoz.IsSampled(); 553 554 common.set_capacity(new_capacity); 555 RawHashSetLayout layout(new_capacity, slot_size, slot_align, has_infoz); 556 void* alloc = policy.get_char_alloc(common); 557 char* mem = static_cast<char*>(policy.alloc(alloc, layout.alloc_size())); 558 const GenerationType old_generation = common.generation(); 559 common.set_generation_ptr( 560 reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); 561 common.set_generation(NextGeneration(old_generation)); 562 563 common.set_control</*kGenerateSeed=*/true>( 564 reinterpret_cast<ctrl_t*>(mem + layout.control_offset())); 565 common.set_slots(mem + layout.slot_offset()); 566 567 size_t total_probe_length = 0; 568 ResetCtrl(common, slot_size); 569 assert(kMode != ResizeNonSooMode::kGuaranteedEmpty || 570 old_capacity == policy.soo_capacity); 571 assert(kMode != ResizeNonSooMode::kGuaranteedAllocated || old_capacity > 0); 572 if constexpr (kMode == ResizeNonSooMode::kGuaranteedAllocated) { 573 total_probe_length = policy.find_new_positions_and_transfer_slots( 574 common, old_ctrl, old_slots, old_capacity); 575 (*policy.dealloc)(alloc, old_capacity, old_ctrl, slot_size, slot_align, 576 has_infoz); 577 } 578 579 ResetGrowthLeft(common); 580 if (has_infoz) { 581 common.set_has_infoz(); 582 infoz.RecordStorageChanged(common.size(), new_capacity); 583 infoz.RecordRehash(total_probe_length); 584 common.set_infoz(infoz); 585 } 586 } 587 588 void ResizeEmptyNonAllocatedTableImpl(CommonFields& common, 589 const PolicyFunctions& policy, 590 size_t new_capacity, bool force_infoz) { 591 assert(IsValidCapacity(new_capacity)); 592 assert(new_capacity > policy.soo_capacity); 593 assert(!force_infoz || policy.soo_capacity > 0); 594 assert(common.capacity() <= policy.soo_capacity); 595 assert(common.empty()); 596 const size_t slot_size = policy.slot_size; 597 HashtablezInfoHandle infoz; 598 const bool should_sample = 599 policy.is_hashtablez_eligible && (force_infoz || ShouldSampleNextTable()); 600 if (ABSL_PREDICT_FALSE(should_sample)) { 601 infoz = ForcedTrySample(slot_size, policy.key_size, policy.value_size, 602 policy.soo_capacity); 603 } 604 ResizeNonSooImpl<ResizeNonSooMode::kGuaranteedEmpty>(common, policy, 605 new_capacity, infoz); 606 } 607 608 // If the table was SOO, initializes new control bytes and transfers slot. 609 // After transferring the slot, sets control and slots in CommonFields. 610 // It is rare to resize an SOO table with one element to a large size. 611 // Requires: `c` contains SOO data. 612 void InsertOldSooSlotAndInitializeControlBytes(CommonFields& c, 613 const PolicyFunctions& policy, 614 size_t hash, ctrl_t* new_ctrl, 615 void* new_slots) { 616 assert(c.size() == policy.soo_capacity); 617 assert(policy.soo_capacity == SooCapacity()); 618 size_t new_capacity = c.capacity(); 619 620 c.generate_new_seed(); 621 size_t offset = probe(c.seed(), new_capacity, hash).offset(); 622 offset = offset == new_capacity ? 0 : offset; 623 SanitizerPoisonMemoryRegion(new_slots, policy.slot_size * new_capacity); 624 void* target_slot = SlotAddress(new_slots, offset, policy.slot_size); 625 SanitizerUnpoisonMemoryRegion(target_slot, policy.slot_size); 626 policy.transfer_n(&c, target_slot, c.soo_data(), 1); 627 c.set_control</*kGenerateSeed=*/false>(new_ctrl); 628 c.set_slots(new_slots); 629 ResetCtrl(c, policy.slot_size); 630 SetCtrl(c, offset, H2(hash), policy.slot_size); 631 } 632 633 enum class ResizeFullSooTableSamplingMode { 634 kNoSampling, 635 // Force sampling. If the table was still not sampled, do not resize. 636 kForceSampleNoResizeIfUnsampled, 637 }; 638 639 void ResizeFullSooTable(CommonFields& common, const PolicyFunctions& policy, 640 size_t new_capacity, 641 ResizeFullSooTableSamplingMode sampling_mode) { 642 assert(common.capacity() == policy.soo_capacity); 643 assert(common.size() == policy.soo_capacity); 644 assert(policy.soo_capacity == SooCapacity()); 645 const size_t slot_size = policy.slot_size; 646 const size_t slot_align = policy.slot_align; 647 648 HashtablezInfoHandle infoz; 649 if (sampling_mode == 650 ResizeFullSooTableSamplingMode::kForceSampleNoResizeIfUnsampled) { 651 if (ABSL_PREDICT_FALSE(policy.is_hashtablez_eligible)) { 652 infoz = ForcedTrySample(slot_size, policy.key_size, policy.value_size, 653 policy.soo_capacity); 654 } 655 656 if (!infoz.IsSampled()) { 657 return; 658 } 659 } 660 661 const bool has_infoz = infoz.IsSampled(); 662 663 common.set_capacity(new_capacity); 664 665 RawHashSetLayout layout(new_capacity, slot_size, slot_align, has_infoz); 666 void* alloc = policy.get_char_alloc(common); 667 char* mem = static_cast<char*>(policy.alloc(alloc, layout.alloc_size())); 668 const GenerationType old_generation = common.generation(); 669 common.set_generation_ptr( 670 reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); 671 common.set_generation(NextGeneration(old_generation)); 672 673 // We do not set control and slots in CommonFields yet to avoid overriding 674 // SOO data. 675 ctrl_t* new_ctrl = reinterpret_cast<ctrl_t*>(mem + layout.control_offset()); 676 void* new_slots = mem + layout.slot_offset(); 677 678 const size_t soo_slot_hash = 679 policy.hash_slot(policy.hash_fn(common), common.soo_data()); 680 681 InsertOldSooSlotAndInitializeControlBytes(common, policy, soo_slot_hash, 682 new_ctrl, new_slots); 683 ResetGrowthLeft(common); 684 if (has_infoz) { 685 common.set_has_infoz(); 686 common.set_infoz(infoz); 687 } 688 } 689 690 void GrowIntoSingleGroupShuffleControlBytes(ctrl_t* __restrict old_ctrl, 691 size_t old_capacity, 692 ctrl_t* __restrict new_ctrl, 693 size_t new_capacity) { 694 assert(is_single_group(new_capacity)); 695 constexpr size_t kHalfWidth = Group::kWidth / 2; 696 ABSL_ASSUME(old_capacity < kHalfWidth); 697 ABSL_ASSUME(old_capacity > 0); 698 static_assert(Group::kWidth == 8 || Group::kWidth == 16, 699 "Group size is not supported."); 700 701 // NOTE: operations are done with compile time known size = 8. 702 // Compiler optimizes that into single ASM operation. 703 704 // Load the bytes from old_capacity. This contains 705 // - the sentinel byte 706 // - all the old control bytes 707 // - the rest is filled with kEmpty bytes 708 // Example: 709 // old_ctrl = 012S012EEEEEEEEE... 710 // copied_bytes = S012EEEE 711 uint64_t copied_bytes = absl::little_endian::Load64(old_ctrl + old_capacity); 712 713 // We change the sentinel byte to kEmpty before storing to both the start of 714 // the new_ctrl, and past the end of the new_ctrl later for the new cloned 715 // bytes. Note that this is faster than setting the sentinel byte to kEmpty 716 // after the copy directly in new_ctrl because we are limited on store 717 // bandwidth. 718 static constexpr uint64_t kEmptyXorSentinel = 719 static_cast<uint8_t>(ctrl_t::kEmpty) ^ 720 static_cast<uint8_t>(ctrl_t::kSentinel); 721 722 // Replace the first byte kSentinel with kEmpty. 723 // Resulting bytes will be shifted by one byte old control blocks. 724 // Example: 725 // old_ctrl = 012S012EEEEEEEEE... 726 // before = S012EEEE 727 // after = E012EEEE 728 copied_bytes ^= kEmptyXorSentinel; 729 730 if (Group::kWidth == 8) { 731 // With group size 8, we can grow with two write operations. 732 assert(old_capacity < 8 && "old_capacity is too large for group size 8"); 733 absl::little_endian::Store64(new_ctrl, copied_bytes); 734 735 static constexpr uint64_t kSentinal64 = 736 static_cast<uint8_t>(ctrl_t::kSentinel); 737 738 // Prepend kSentinel byte to the beginning of copied_bytes. 739 // We have maximum 3 non-empty bytes at the beginning of copied_bytes for 740 // group size 8. 741 // Example: 742 // old_ctrl = 012S012EEEE 743 // before = E012EEEE 744 // after = SE012EEE 745 copied_bytes = (copied_bytes << 8) ^ kSentinal64; 746 absl::little_endian::Store64(new_ctrl + new_capacity, copied_bytes); 747 // Example for capacity 3: 748 // old_ctrl = 012S012EEEE 749 // After the first store: 750 // >! 751 // new_ctrl = E012EEEE??????? 752 // After the second store: 753 // >! 754 // new_ctrl = E012EEESE012EEE 755 return; 756 } 757 758 assert(Group::kWidth == 16); 759 760 // Fill the second half of the main control bytes with kEmpty. 761 // For small capacity that may write into mirrored control bytes. 762 // It is fine as we will overwrite all the bytes later. 763 std::memset(new_ctrl + kHalfWidth, static_cast<int8_t>(ctrl_t::kEmpty), 764 kHalfWidth); 765 // Fill the second half of the mirrored control bytes with kEmpty. 766 std::memset(new_ctrl + new_capacity + kHalfWidth, 767 static_cast<int8_t>(ctrl_t::kEmpty), kHalfWidth); 768 // Copy the first half of the non-mirrored control bytes. 769 absl::little_endian::Store64(new_ctrl, copied_bytes); 770 new_ctrl[new_capacity] = ctrl_t::kSentinel; 771 // Copy the first half of the mirrored control bytes. 772 absl::little_endian::Store64(new_ctrl + new_capacity + 1, copied_bytes); 773 774 // Example for growth capacity 1->3: 775 // old_ctrl = 0S0EEEEEEEEEEEEEE 776 // new_ctrl at the end = E0ESE0EEEEEEEEEEEEE 777 // >! 778 // new_ctrl after 1st memset = ????????EEEEEEEE??? 779 // >! 780 // new_ctrl after 2nd memset = ????????EEEEEEEEEEE 781 // >! 782 // new_ctrl after 1st store = E0EEEEEEEEEEEEEEEEE 783 // new_ctrl after kSentinel = E0ESEEEEEEEEEEEEEEE 784 // >! 785 // new_ctrl after 2nd store = E0ESE0EEEEEEEEEEEEE 786 787 // Example for growth capacity 3->7: 788 // old_ctrl = 012S012EEEEEEEEEEEE 789 // new_ctrl at the end = E012EEESE012EEEEEEEEEEE 790 // >! 791 // new_ctrl after 1st memset = ????????EEEEEEEE??????? 792 // >! 793 // new_ctrl after 2nd memset = ????????EEEEEEEEEEEEEEE 794 // >! 795 // new_ctrl after 1st store = E012EEEEEEEEEEEEEEEEEEE 796 // new_ctrl after kSentinel = E012EEESEEEEEEEEEEEEEEE 797 // >! 798 // new_ctrl after 2nd store = E012EEESE012EEEEEEEEEEE 799 800 // Example for growth capacity 7->15: 801 // old_ctrl = 0123456S0123456EEEEEEEE 802 // new_ctrl at the end = E0123456EEEEEEESE0123456EEEEEEE 803 // >! 804 // new_ctrl after 1st memset = ????????EEEEEEEE??????????????? 805 // >! 806 // new_ctrl after 2nd memset = ????????EEEEEEEE???????EEEEEEEE 807 // >! 808 // new_ctrl after 1st store = E0123456EEEEEEEE???????EEEEEEEE 809 // new_ctrl after kSentinel = E0123456EEEEEEES???????EEEEEEEE 810 // >! 811 // new_ctrl after 2nd store = E0123456EEEEEEESE0123456EEEEEEE 812 } 813 814 size_t GrowToNextCapacityAndPrepareInsert(CommonFields& common, 815 const PolicyFunctions& policy, 816 size_t new_hash) { 817 assert(common.growth_left() == 0); 818 const size_t old_capacity = common.capacity(); 819 assert(old_capacity == 0 || old_capacity > policy.soo_capacity); 820 821 const size_t new_capacity = NextCapacity(old_capacity); 822 assert(IsValidCapacity(new_capacity)); 823 assert(new_capacity > policy.soo_capacity); 824 825 ctrl_t* old_ctrl = common.control(); 826 void* old_slots = common.slot_array(); 827 828 common.set_capacity(new_capacity); 829 const size_t slot_size = policy.slot_size; 830 const size_t slot_align = policy.slot_align; 831 HashtablezInfoHandle infoz; 832 if (old_capacity > 0) { 833 infoz = common.infoz(); 834 } else { 835 const bool should_sample = 836 policy.is_hashtablez_eligible && ShouldSampleNextTable(); 837 if (ABSL_PREDICT_FALSE(should_sample)) { 838 infoz = ForcedTrySample(slot_size, policy.key_size, policy.value_size, 839 policy.soo_capacity); 840 } 841 } 842 const bool has_infoz = infoz.IsSampled(); 843 844 RawHashSetLayout layout(new_capacity, slot_size, slot_align, has_infoz); 845 void* alloc = policy.get_char_alloc(common); 846 char* mem = static_cast<char*>(policy.alloc(alloc, layout.alloc_size())); 847 const GenerationType old_generation = common.generation(); 848 common.set_generation_ptr( 849 reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); 850 common.set_generation(NextGeneration(old_generation)); 851 852 ctrl_t* new_ctrl = reinterpret_cast<ctrl_t*>(mem + layout.control_offset()); 853 void* new_slots = mem + layout.slot_offset(); 854 common.set_control</*kGenerateSeed=*/false>(new_ctrl); 855 common.set_slots(new_slots); 856 857 h2_t new_h2 = H2(new_hash); 858 size_t total_probe_length = 0; 859 FindInfo find_info; 860 if (old_capacity == 0) { 861 static_assert(NextCapacity(0) == 1); 862 InitializeSingleElementControlBytes(new_h2, new_ctrl); 863 common.generate_new_seed(); 864 find_info = FindInfo{0, 0}; 865 } else { 866 if (ABSL_PREDICT_TRUE(is_single_group(new_capacity))) { 867 GrowIntoSingleGroupShuffleControlBytes(old_ctrl, old_capacity, new_ctrl, 868 new_capacity); 869 // Single group tables have all slots full on resize. So we can transfer 870 // all slots without checking the control bytes. 871 assert(common.size() == old_capacity); 872 policy.transfer_n(&common, NextSlot(new_slots, slot_size), old_slots, 873 old_capacity); 874 PoisonEmptySlots(common, slot_size); 875 // We put the new element either at the beginning or at the end of the 876 // table with approximately equal probability. 877 size_t offset = SingleGroupTableH1(new_hash, common.seed()) & 1 878 ? 0 879 : new_capacity - 1; 880 881 assert(IsEmpty(new_ctrl[offset])); 882 SetCtrlInSingleGroupTable(common, offset, new_h2, policy.slot_size); 883 find_info = FindInfo{offset, 0}; 884 } else { 885 ResetCtrl(common, slot_size); 886 total_probe_length = policy.find_new_positions_and_transfer_slots( 887 common, old_ctrl, old_slots, old_capacity); 888 find_info = find_first_non_full(common, new_hash); 889 SetCtrlInLargeTable(common, find_info.offset, new_h2, policy.slot_size); 890 } 891 assert(old_capacity > policy.soo_capacity); 892 (*policy.dealloc)(alloc, old_capacity, old_ctrl, slot_size, slot_align, 893 has_infoz); 894 } 895 PrepareInsertCommon(common); 896 ResetGrowthLeft(common); 897 898 if (ABSL_PREDICT_FALSE(has_infoz)) { 899 common.set_has_infoz(); 900 infoz.RecordStorageChanged(common.size() - 1, new_capacity); 901 infoz.RecordRehash(total_probe_length); 902 infoz.RecordInsert(new_hash, find_info.probe_length); 903 common.set_infoz(infoz); 904 } 905 return find_info.offset; 906 } 907 908 // Called whenever the table needs to vacate empty slots either by removing 909 // tombstones via rehash or growth to next capacity. 910 ABSL_ATTRIBUTE_NOINLINE 911 size_t RehashOrGrowToNextCapacityAndPrepareInsert(CommonFields& common, 912 const PolicyFunctions& policy, 913 size_t new_hash) { 914 const size_t cap = common.capacity(); 915 ABSL_ASSUME(cap > 0); 916 if (cap > Group::kWidth && 917 // Do these calculations in 64-bit to avoid overflow. 918 common.size() * uint64_t{32} <= cap * uint64_t{25}) { 919 // Squash DELETED without growing if there is enough capacity. 920 // 921 // Rehash in place if the current size is <= 25/32 of capacity. 922 // Rationale for such a high factor: 1) DropDeletesWithoutResize() is 923 // faster than resize, and 2) it takes quite a bit of work to add 924 // tombstones. In the worst case, seems to take approximately 4 925 // insert/erase pairs to create a single tombstone and so if we are 926 // rehashing because of tombstones, we can afford to rehash-in-place as 927 // long as we are reclaiming at least 1/8 the capacity without doing more 928 // than 2X the work. (Where "work" is defined to be size() for rehashing 929 // or rehashing in place, and 1 for an insert or erase.) But rehashing in 930 // place is faster per operation than inserting or even doubling the size 931 // of the table, so we actually afford to reclaim even less space from a 932 // resize-in-place. The decision is to rehash in place if we can reclaim 933 // at about 1/8th of the usable capacity (specifically 3/28 of the 934 // capacity) which means that the total cost of rehashing will be a small 935 // fraction of the total work. 936 // 937 // Here is output of an experiment using the BM_CacheInSteadyState 938 // benchmark running the old case (where we rehash-in-place only if we can 939 // reclaim at least 7/16*capacity) vs. this code (which rehashes in place 940 // if we can recover 3/32*capacity). 941 // 942 // Note that although in the worst-case number of rehashes jumped up from 943 // 15 to 190, but the number of operations per second is almost the same. 944 // 945 // Abridged output of running BM_CacheInSteadyState benchmark from 946 // raw_hash_set_benchmark. N is the number of insert/erase operations. 947 // 948 // | OLD (recover >= 7/16 | NEW (recover >= 3/32) 949 // size | N/s LoadFactor NRehashes | N/s LoadFactor NRehashes 950 // 448 | 145284 0.44 18 | 140118 0.44 19 951 // 493 | 152546 0.24 11 | 151417 0.48 28 952 // 538 | 151439 0.26 11 | 151152 0.53 38 953 // 583 | 151765 0.28 11 | 150572 0.57 50 954 // 628 | 150241 0.31 11 | 150853 0.61 66 955 // 672 | 149602 0.33 12 | 150110 0.66 90 956 // 717 | 149998 0.35 12 | 149531 0.70 129 957 // 762 | 149836 0.37 13 | 148559 0.74 190 958 // 807 | 149736 0.39 14 | 151107 0.39 14 959 // 852 | 150204 0.42 15 | 151019 0.42 15 960 return DropDeletesWithoutResizeAndPrepareInsert(common, policy, new_hash); 961 } else { 962 // Otherwise grow the container. 963 return GrowToNextCapacityAndPrepareInsert(common, policy, new_hash); 964 } 965 } 966 967 // Slow path for PrepareInsertNonSoo that is called when the table has deleted 968 // slots or need to be resized or rehashed. 969 size_t PrepareInsertNonSooSlow(CommonFields& common, 970 const PolicyFunctions& policy, size_t hash) { 971 const GrowthInfo growth_info = common.growth_info(); 972 assert(!growth_info.HasNoDeletedAndGrowthLeft()); 973 if (ABSL_PREDICT_TRUE(growth_info.HasNoGrowthLeftAndNoDeleted())) { 974 // Table without deleted slots (>95% cases) that needs to be resized. 975 assert(growth_info.HasNoDeleted() && growth_info.GetGrowthLeft() == 0); 976 return GrowToNextCapacityAndPrepareInsert(common, policy, hash); 977 } 978 if (ABSL_PREDICT_FALSE(growth_info.HasNoGrowthLeftAssumingMayHaveDeleted())) { 979 // Table with deleted slots that needs to be rehashed or resized. 980 return RehashOrGrowToNextCapacityAndPrepareInsert(common, policy, hash); 981 } 982 // Table with deleted slots that has space for the inserting element. 983 FindInfo target = find_first_non_full(common, hash); 984 PrepareInsertCommon(common); 985 common.growth_info().OverwriteControlAsFull(common.control()[target.offset]); 986 SetCtrlInLargeTable(common, target.offset, H2(hash), policy.slot_size); 987 common.infoz().RecordInsert(hash, target.probe_length); 988 return target.offset; 989 } 990 991 } // namespace 992 993 void* GetRefForEmptyClass(CommonFields& common) { 994 // Empty base optimization typically make the empty base class address to be 995 // the same as the first address of the derived class object. 996 // But we generally assume that for empty classes we can return any valid 997 // pointer. 998 return &common; 999 } 1000 1001 void ResizeAllocatedTableWithSeedChange(CommonFields& common, 1002 const PolicyFunctions& policy, 1003 size_t new_capacity) { 1004 ResizeNonSooImpl<ResizeNonSooMode::kGuaranteedAllocated>( 1005 common, policy, new_capacity, common.infoz()); 1006 } 1007 1008 void ReserveEmptyNonAllocatedTableToFitNewSize(CommonFields& common, 1009 const PolicyFunctions& policy, 1010 size_t new_size) { 1011 ValidateMaxSize(new_size, policy.slot_size); 1012 ResizeEmptyNonAllocatedTableImpl( 1013 common, policy, NormalizeCapacity(GrowthToLowerboundCapacity(new_size)), 1014 /*force_infoz=*/false); 1015 // This is after resize, to ensure that we have completed the allocation 1016 // and have potentially sampled the hashtable. 1017 common.infoz().RecordReservation(new_size); 1018 common.reset_reserved_growth(new_size); 1019 common.set_reservation_size(new_size); 1020 } 1021 1022 void ReserveEmptyNonAllocatedTableToFitBucketCount( 1023 CommonFields& common, const PolicyFunctions& policy, size_t bucket_count) { 1024 size_t new_capacity = NormalizeCapacity(bucket_count); 1025 ValidateMaxSize(CapacityToGrowth(new_capacity), policy.slot_size); 1026 ResizeEmptyNonAllocatedTableImpl(common, policy, new_capacity, 1027 /*force_infoz=*/false); 1028 } 1029 1030 void GrowEmptySooTableToNextCapacityForceSampling( 1031 CommonFields& common, const PolicyFunctions& policy) { 1032 ResizeEmptyNonAllocatedTableImpl(common, policy, NextCapacity(SooCapacity()), 1033 /*force_infoz=*/true); 1034 } 1035 1036 // Resizes a full SOO table to the NextCapacity(SooCapacity()). 1037 template <size_t SooSlotMemcpySize, bool TransferUsesMemcpy> 1038 void GrowFullSooTableToNextCapacity(CommonFields& common, 1039 const PolicyFunctions& policy, 1040 size_t soo_slot_hash) { 1041 assert(common.capacity() == policy.soo_capacity); 1042 assert(common.size() == policy.soo_capacity); 1043 static constexpr size_t kNewCapacity = NextCapacity(SooCapacity()); 1044 assert(kNewCapacity > policy.soo_capacity); 1045 assert(policy.soo_capacity == SooCapacity()); 1046 const size_t slot_size = policy.slot_size; 1047 const size_t slot_align = policy.slot_align; 1048 common.set_capacity(kNewCapacity); 1049 1050 // Since the table is not empty, it will not be sampled. 1051 // The decision to sample was already made during the first insertion. 1052 RawHashSetLayout layout(kNewCapacity, slot_size, slot_align, 1053 /*has_infoz=*/false); 1054 void* alloc = policy.get_char_alloc(common); 1055 char* mem = static_cast<char*>(policy.alloc(alloc, layout.alloc_size())); 1056 const GenerationType old_generation = common.generation(); 1057 common.set_generation_ptr( 1058 reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); 1059 common.set_generation(NextGeneration(old_generation)); 1060 1061 // We do not set control and slots in CommonFields yet to avoid overriding 1062 // SOO data. 1063 ctrl_t* new_ctrl = reinterpret_cast<ctrl_t*>(mem + layout.control_offset()); 1064 void* new_slots = mem + layout.slot_offset(); 1065 1066 InitializeThreeElementsControlBytesAfterSoo(soo_slot_hash, new_ctrl); 1067 1068 SanitizerPoisonMemoryRegion(new_slots, slot_size * kNewCapacity); 1069 void* target_slot = SlotAddress(new_slots, SooSlotIndex(), slot_size); 1070 SanitizerUnpoisonMemoryRegion(target_slot, slot_size); 1071 if constexpr (TransferUsesMemcpy) { 1072 // Target slot is placed at index 1, but capacity is at 1073 // minimum 3. So we are allowed to copy at least twice as much 1074 // memory. 1075 static_assert(SooSlotIndex() == 1); 1076 static_assert(SooSlotMemcpySize > 0); 1077 static_assert(SooSlotMemcpySize <= MaxSooSlotSize()); 1078 assert(SooSlotMemcpySize <= 2 * slot_size); 1079 assert(SooSlotMemcpySize >= slot_size); 1080 void* next_slot = SlotAddress(target_slot, 1, slot_size); 1081 SanitizerUnpoisonMemoryRegion(next_slot, SooSlotMemcpySize - slot_size); 1082 std::memcpy(target_slot, common.soo_data(), SooSlotMemcpySize); 1083 SanitizerPoisonMemoryRegion(next_slot, SooSlotMemcpySize - slot_size); 1084 } else { 1085 static_assert(SooSlotMemcpySize == 0); 1086 policy.transfer_n(&common, target_slot, common.soo_data(), 1); 1087 } 1088 common.set_control</*kGenerateSeed=*/true>(new_ctrl); 1089 common.set_slots(new_slots); 1090 1091 ResetGrowthLeft(common); 1092 } 1093 1094 void GrowFullSooTableToNextCapacityForceSampling( 1095 CommonFields& common, const PolicyFunctions& policy) { 1096 assert(common.capacity() == policy.soo_capacity); 1097 assert(common.size() == policy.soo_capacity); 1098 assert(policy.soo_capacity == SooCapacity()); 1099 ResizeFullSooTable( 1100 common, policy, NextCapacity(SooCapacity()), 1101 ResizeFullSooTableSamplingMode::kForceSampleNoResizeIfUnsampled); 1102 } 1103 1104 void Rehash(CommonFields& common, const PolicyFunctions& policy, size_t n) { 1105 const size_t cap = common.capacity(); 1106 1107 auto clear_backing_array = [&]() { 1108 ClearBackingArray(common, policy, policy.get_char_alloc(common), 1109 /*reuse=*/false, policy.soo_capacity > 0); 1110 }; 1111 1112 const size_t slot_size = policy.slot_size; 1113 1114 if (n == 0) { 1115 if (cap <= policy.soo_capacity) return; 1116 if (common.empty()) { 1117 clear_backing_array(); 1118 return; 1119 } 1120 if (common.size() <= policy.soo_capacity) { 1121 // When the table is already sampled, we keep it sampled. 1122 if (common.infoz().IsSampled()) { 1123 static constexpr size_t kInitialSampledCapacity = 1124 NextCapacity(SooCapacity()); 1125 if (cap > kInitialSampledCapacity) { 1126 ResizeAllocatedTableWithSeedChange(common, policy, 1127 kInitialSampledCapacity); 1128 } 1129 // This asserts that we didn't lose sampling coverage in `resize`. 1130 assert(common.infoz().IsSampled()); 1131 return; 1132 } 1133 assert(slot_size <= sizeof(HeapOrSoo)); 1134 assert(policy.slot_align <= alignof(HeapOrSoo)); 1135 HeapOrSoo tmp_slot(uninitialized_tag_t{}); 1136 size_t begin_offset = FindFirstFullSlot(0, cap, common.control()); 1137 policy.transfer_n( 1138 &common, &tmp_slot, 1139 SlotAddress(common.slot_array(), begin_offset, slot_size), 1); 1140 clear_backing_array(); 1141 policy.transfer_n(&common, common.soo_data(), &tmp_slot, 1); 1142 common.set_full_soo(); 1143 return; 1144 } 1145 } 1146 1147 // bitor is a faster way of doing `max` here. We will round up to the next 1148 // power-of-2-minus-1, so bitor is good enough. 1149 size_t new_size = n | GrowthToLowerboundCapacity(common.size()); 1150 ValidateMaxSize(n, policy.slot_size); 1151 const size_t new_capacity = NormalizeCapacity(new_size); 1152 // n == 0 unconditionally rehashes as per the standard. 1153 if (n == 0 || new_capacity > cap) { 1154 if (cap == policy.soo_capacity) { 1155 if (common.empty()) { 1156 ResizeEmptyNonAllocatedTableImpl(common, policy, new_capacity, 1157 /*force_infoz=*/false); 1158 } else { 1159 ResizeFullSooTable(common, policy, new_capacity, 1160 ResizeFullSooTableSamplingMode::kNoSampling); 1161 } 1162 } else { 1163 ResizeAllocatedTableWithSeedChange(common, policy, new_capacity); 1164 } 1165 // This is after resize, to ensure that we have completed the allocation 1166 // and have potentially sampled the hashtable. 1167 common.infoz().RecordReservation(n); 1168 } 1169 } 1170 1171 void ReserveAllocatedTable(CommonFields& common, const PolicyFunctions& policy, 1172 size_t n) { 1173 common.reset_reserved_growth(n); 1174 common.set_reservation_size(n); 1175 1176 const size_t cap = common.capacity(); 1177 assert(!common.empty() || cap > policy.soo_capacity); 1178 assert(cap > 0); 1179 const size_t max_size_before_growth = 1180 cap <= policy.soo_capacity ? policy.soo_capacity 1181 : common.size() + common.growth_left(); 1182 if (n <= max_size_before_growth) { 1183 return; 1184 } 1185 ValidateMaxSize(n, policy.slot_size); 1186 const size_t new_capacity = NormalizeCapacity(GrowthToLowerboundCapacity(n)); 1187 if (cap == policy.soo_capacity) { 1188 assert(!common.empty()); 1189 ResizeFullSooTable(common, policy, new_capacity, 1190 ResizeFullSooTableSamplingMode::kNoSampling); 1191 } else { 1192 assert(cap > policy.soo_capacity); 1193 ResizeAllocatedTableWithSeedChange(common, policy, new_capacity); 1194 } 1195 common.infoz().RecordReservation(n); 1196 } 1197 1198 size_t PrepareInsertNonSoo(CommonFields& common, const PolicyFunctions& policy, 1199 size_t hash, FindInfo target) { 1200 const bool rehash_for_bug_detection = 1201 common.should_rehash_for_bug_detection_on_insert() && 1202 // Required to allow use of ResizeAllocatedTable. 1203 common.capacity() > 0; 1204 if (rehash_for_bug_detection) { 1205 // Move to a different heap allocation in order to detect bugs. 1206 const size_t cap = common.capacity(); 1207 ResizeAllocatedTableWithSeedChange( 1208 common, policy, common.growth_left() > 0 ? cap : NextCapacity(cap)); 1209 target = find_first_non_full(common, hash); 1210 } 1211 1212 const GrowthInfo growth_info = common.growth_info(); 1213 // When there are no deleted slots in the table 1214 // and growth_left is positive, we can insert at the first 1215 // empty slot in the probe sequence (target). 1216 if (ABSL_PREDICT_FALSE(!growth_info.HasNoDeletedAndGrowthLeft())) { 1217 return PrepareInsertNonSooSlow(common, policy, hash); 1218 } 1219 PrepareInsertCommon(common); 1220 common.growth_info().OverwriteEmptyAsFull(); 1221 SetCtrl(common, target.offset, H2(hash), policy.slot_size); 1222 common.infoz().RecordInsert(hash, target.probe_length); 1223 return target.offset; 1224 } 1225 1226 namespace { 1227 // Returns true if the following is true 1228 // 1. OptimalMemcpySizeForSooSlotTransfer(left) > 1229 // OptimalMemcpySizeForSooSlotTransfer(left - 1) 1230 // 2. OptimalMemcpySizeForSooSlotTransfer(left) are equal for all i in [left, 1231 // right]. 1232 // This function is used to verify that we have all the possible template 1233 // instantiations for GrowFullSooTableToNextCapacity. 1234 // With this verification the problem may be detected at compile time instead of 1235 // link time. 1236 constexpr bool VerifyOptimalMemcpySizeForSooSlotTransferRange(size_t left, 1237 size_t right) { 1238 size_t optimal_size_for_range = OptimalMemcpySizeForSooSlotTransfer(left); 1239 if (optimal_size_for_range <= OptimalMemcpySizeForSooSlotTransfer(left - 1)) { 1240 return false; 1241 } 1242 for (size_t i = left + 1; i <= right; ++i) { 1243 if (OptimalMemcpySizeForSooSlotTransfer(i) != optimal_size_for_range) { 1244 return false; 1245 } 1246 } 1247 return true; 1248 } 1249 } // namespace 1250 1251 // We need to instantiate ALL possible template combinations because we define 1252 // the function in the cc file. 1253 template void GrowFullSooTableToNextCapacity<0, false>(CommonFields&, 1254 const PolicyFunctions&, 1255 size_t); 1256 template void 1257 GrowFullSooTableToNextCapacity<OptimalMemcpySizeForSooSlotTransfer(1), true>( 1258 CommonFields&, const PolicyFunctions&, size_t); 1259 1260 static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(2, 3)); 1261 template void 1262 GrowFullSooTableToNextCapacity<OptimalMemcpySizeForSooSlotTransfer(3), true>( 1263 CommonFields&, const PolicyFunctions&, size_t); 1264 1265 static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(4, 8)); 1266 template void 1267 GrowFullSooTableToNextCapacity<OptimalMemcpySizeForSooSlotTransfer(8), true>( 1268 CommonFields&, const PolicyFunctions&, size_t); 1269 1270 #if UINTPTR_MAX == UINT32_MAX 1271 static_assert(MaxSooSlotSize() == 8); 1272 #else 1273 static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(9, 16)); 1274 template void 1275 GrowFullSooTableToNextCapacity<OptimalMemcpySizeForSooSlotTransfer(16), true>( 1276 CommonFields&, const PolicyFunctions&, size_t); 1277 static_assert(MaxSooSlotSize() == 16); 1278 #endif 1279 1280 } // namespace container_internal 1281 ABSL_NAMESPACE_END 1282 } // namespace absl