Test the single size bit when checking emptiness of small SwissTables. Small SwissTables (`capacity <= 1`) have `size() <= 1`, yet small-table paths checked `empty()` via `data_ < kSizeOneNoMetadata`, requiring a 64-bit unsigned comparison instead of testing the single `kSizeOneNoMetadata` bit. In addition, `is_soo()` redundantly checked `cap.IsValid() && fits_in_soo(cap.capacity())` even though `SooCapacity() == kMaxSmallCapacity` and `HashtableCapacity::is_small()` already rejects `kInvalid` and `kSelfMovedFrom`. Add `empty_when_small()` (`(data_ & kSizeOneNoMetadata) == 0`) and use it across small-table lookup, insertion, merge, clear, and destruction paths where `is_small()` already holds, compiling to a 32-bit `testl` on x86 and a single `tbz`/`tbnz` on ARM64. Simplify `is_soo()` to `SooEnabled() && maybe_invalid_capacity().is_small()` and remove the unused `fits_in_soo()` helper. PiperOrigin-RevId: 1005230808
diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index baed443..e60b237 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc
@@ -852,7 +852,7 @@ ABSL_SWISSTABLE_ASSERT(c.capacity() > 0); if (policy.destroy_slot != nullptr) { if (c.is_small()) { - if (!c.empty()) { + if (!c.empty_when_small()) { static_assert(kMaxSmallCapacity == 1); policy.destroy_slot(&c, c.single_non_soo_slot()); } @@ -948,7 +948,7 @@ if (c.is_small()) { // In the small case, we keep the backing array allocation because the // memory overhead is small compared to the potential benefits of reuse. - if (!c.empty()) { + if (!c.empty_when_small()) { if (destroy_slot != nullptr) { destroy_slot(&c, SingleSlotAddress<kSooEnabled>(c)); } @@ -2039,7 +2039,7 @@ void* hasher, HashForKeyFn get_hash) { ABSL_SWISSTABLE_ASSERT(common.is_small()); ABSL_SWISSTABLE_ASSERT(!policy.soo_enabled); - const bool is_empty = common.empty(); + const bool is_empty = common.empty_when_small(); if (common.capacity() == 1 && is_empty) { IncrementSmallSizeNonSoo(common, policy); // Compute before `RecordInsertMissCold` to avoid reloading `control_`. @@ -2052,7 +2052,7 @@ return res; } - const size_t size = common.size(); + const size_t size = is_empty ? 0 : 1; const bool has_infoz = is_empty ? (policy.is_hashtablez_enabled && ShouldSampleNextTable()) : common.has_infoz();
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index ba34be9..e9cdbd2 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h
@@ -853,6 +853,12 @@ void decrement_size() { data_ -= kSizeOneNoMetadata; } // Returns true if the table is empty. bool empty() const { return data_ < kSizeOneNoMetadata; } + // Returns true if a small table (capacity <= 1) is empty. + bool empty_when_small() const { + ABSL_SWISSTABLE_ASSERT(is_small()); + ABSL_SWISSTABLE_ASSERT(size() <= 1); + return (data_ & kSizeOneNoMetadata) == 0; + } // Returns true if an empty SOO table has already queried should_sample_soo(). bool soo_has_tried_sampling() const { @@ -1419,6 +1425,7 @@ inline_data_.decrement_size(); } bool empty() const { return inline_data_.empty(); } + bool empty_when_small() const { return inline_data_.empty_when_small(); } void set_soo_has_tried_sampling() { inline_data_.set_soo_has_tried_sampling(); } @@ -2515,18 +2522,15 @@ return SizeToCapacity(MaxValidSize()); } - // Whether `size` fits in the SOO capacity of this table. - bool fits_in_soo(size_t size) const { - return SooEnabled() && size <= SooCapacity(); - } // Whether this table is in SOO mode or non-SOO mode. bool is_soo() const { - HashtableCapacity cap = maybe_invalid_capacity(); - return cap.IsValid() && fits_in_soo(cap.capacity()); + static_assert(SooCapacity() == kMaxSmallCapacity); + return SooEnabled() && maybe_invalid_capacity().is_small(); } - bool is_full_soo() const { return is_soo() && !empty(); } + bool is_full_soo() const { return is_soo() && !empty_when_small(); } bool is_small() const { return common().is_small(); } + bool empty_when_small() const { return common().empty_when_small(); } // Give an early error when key_type is not hashable/eq. auto KeyTypeCanBeHashed(const Hash& h, const key_type& k) -> decltype(h(k)); @@ -3351,7 +3355,7 @@ }; if (src.is_small()) { - if (src.empty()) return; + if (src.empty_when_small()) return; if (insert_slot(src.single_slot())) src.erase_meta_only_small(); return; @@ -3613,7 +3617,9 @@ // SOO functionality. template <class K> ABSL_ATTRIBUTE_ALWAYS_INLINE iterator find_small(const K& key) { - return empty() || !equal_to(key, single_slot()) ? end() : single_iterator(); + return empty_when_small() || !equal_to(key, single_slot()) + ? end() + : single_iterator(); } template <class K> @@ -3687,8 +3693,8 @@ kIsDefaultAlloc && BackingArrayAlignment(alignof(slot_type)) == kStandardBackingArrayAlignment; if constexpr (SooEnabled()) { - if (is_small() && - (PolicyTraits::template destroy_is_trivial<Alloc>() || empty())) { + if (is_small() && (PolicyTraits::template destroy_is_trivial<Alloc>() || + empty_when_small())) { return; } } else { @@ -3850,7 +3856,7 @@ find_or_prepare_insert_soo(const K& key) { bool force_sampling; slot_type* slot = single_slot(); - if (empty()) { + if (empty_when_small()) { if (!should_sample_soo()) { common().set_full_soo(); return {slot, true}; @@ -3880,7 +3886,7 @@ if constexpr (SooEnabled()) { return find_or_prepare_insert_soo(key); } - if (!empty()) { + if (!empty_when_small()) { if (equal_to(key, single_slot())) { return {single_slot(), false}; }