From c508bab517c1a04275ca80b144b3c22f2f3751c6 Mon Sep 17 00:00:00 2001 From: Vitaly Goldshteyn Date: Wed, 20 May 2026 03:22:32 -0700 Subject: [PATCH] Do not reserve space for GrowthInfo for single element tables in non-SOO case. That would save 8 bytes of allocation size for such tables. PiperOrigin-RevId: 918340255 Change-Id: Ic5f00dfb87392089ac04242418e4f55cc599619e --- absl/container/internal/raw_hash_set.cc | 15 ++++++++---- absl/container/internal/raw_hash_set.h | 25 +++++++++++++------- absl/container/internal/raw_hash_set_test.cc | 12 ++++++++++ 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index 675e3a30..ef680e79 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc @@ -822,11 +822,15 @@ void ResizeNonSooImpl(CommonFields& common, common, policy, old_ctrl, old_slots, old_capacity); (*policy.dealloc)(alloc, old_capacity, old_ctrl, slot_size, slot_align, has_infoz); - ResetGrowthLeft(GetGrowthInfoFromControl(new_ctrl), new_capacity, - common.size()); + if (HasGrowthInfoForCapacity(new_capacity)) { + ResetGrowthLeft(GetGrowthInfoFromControl(new_ctrl), new_capacity, + common.size()); + } } else { - GetGrowthInfoFromControl(new_ctrl).InitGrowthLeftNoDeleted( - CapacityToGrowth(new_capacity)); + if (HasGrowthInfoForCapacity(new_capacity)) { + GetGrowthInfoFromControl(new_ctrl).InitGrowthLeftNoDeleted( + CapacityToGrowth(new_capacity)); + } } if (ABSL_PREDICT_FALSE(has_infoz)) { @@ -1215,7 +1219,8 @@ class ProbedItemEncoder { ProbedItem* OverflowBufferStart() const { // We reuse GrowthInfo memory as well. - return AlignToNextItem(control_ - ControlOffset(/*has_infoz=*/false)); + return AlignToNextItem(control_ - ControlOffset(/*has_infoz=*/false, + /*has_growth_info=*/true)); } // Encodes item when previously allocated buffer is full. diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index 191b1c9b..2a08ec5c 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h @@ -989,10 +989,20 @@ constexpr size_t NumControlBytes(size_t capacity) { return IsSmallCapacity(capacity) ? 0 : capacity + 1 + NumClonedBytes(); } +// Returns whether table with the given capacity has a GrowthInfo. +constexpr bool HasGrowthInfoForCapacity(size_t capacity) { + return !IsSmallCapacity(capacity); +} + // Computes the offset from the start of the backing allocation of control. // infoz and growth_info are stored at the beginning of the backing array. -constexpr size_t ControlOffset(bool has_infoz) { - return (has_infoz ? sizeof(HashtablezInfoHandle) : 0) + sizeof(GrowthInfo); +constexpr size_t ControlOffset(bool has_infoz, bool has_growth_info) { + if (ABSL_PREDICT_FALSE(has_infoz)) { + // We always allocate GrowthInfo for sampled tables to allow branchless + // access to infoz pointer. + return sizeof(HashtablezInfoHandle) + sizeof(GrowthInfo); + } + return has_growth_info ? sizeof(GrowthInfo) : 0; } // Returns the offset of the next item after `offset` that is aligned to `align` @@ -1004,12 +1014,10 @@ constexpr size_t AlignUpTo(size_t offset, size_t align) { // Helper class for computing offsets and allocation size of hash set fields. class RawHashSetLayout { public: - // TODO(b/413062340): maybe don't allocate growth info for capacity 1 tables. - // Doing so may require additional branches/complexity so it might not be - // worth it. explicit RawHashSetLayout(size_t capacity, size_t slot_size, size_t slot_align, bool has_infoz) - : control_offset_(ControlOffset(has_infoz)), + : control_offset_( + ControlOffset(has_infoz, HasGrowthInfoForCapacity(capacity))), generation_offset_(control_offset_ + NumControlBytes(capacity)), slot_offset_( AlignUpTo(generation_offset_ + NumGenerationBytes(), slot_align)), @@ -1240,7 +1248,7 @@ class CommonFields : public CommonFieldsGenerationInfo { size_t growth_left() const { return growth_info().GetGrowthLeft(); } GrowthInfo& growth_info() { - ABSL_SWISSTABLE_ASSERT(!is_small()); + ABSL_SWISSTABLE_ASSERT(HasGrowthInfoForCapacity(capacity())); return GetGrowthInfoFromControl(control()); } GrowthInfo growth_info() const { @@ -1259,7 +1267,8 @@ class CommonFields : public CommonFieldsGenerationInfo { reinterpret_cast(control()) % alignof(size_t) == 0); ABSL_SWISSTABLE_ASSERT(has_infoz()); return reinterpret_cast( - control() - ControlOffset(/*has_infoz=*/true)); + control() - ControlOffset(/*has_infoz=*/true, + HasGrowthInfoForCapacity(capacity()))); } HashtablezInfoHandle infoz() { diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index 3f81f26e..176fefbd 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc @@ -2772,6 +2772,18 @@ TYPED_TEST(SooTest, HintInsert) { EXPECT_TRUE(node); // NOLINT(bugprone-use-after-move) } +TYPED_TEST(SooTest, RehashZeroForSmallTable) { + TypeParam t{0}; + EXPECT_EQ(t.capacity(), 1); + t.rehash(0); + EXPECT_EQ(t.capacity(), 1); + EXPECT_TRUE(t.contains(0)); + t.insert(1); + EXPECT_EQ(t.capacity(), NextCapacity(1)); + EXPECT_TRUE(t.contains(0)); + EXPECT_TRUE(t.contains(1)); +} + template T MakeSimpleTable(size_t size, bool do_reserve) { T t;