Skip to content

Commit

Permalink
Type erase IterateOverFullSlots to decrease code size.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 725747503
Change-Id: I5dbd8229b723e236402c3823221ff315ac107873
  • Loading branch information
ezbr authored and copybara-github committed Feb 11, 2025
1 parent ce571f2 commit 05e72a3
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 91 deletions.
1 change: 1 addition & 0 deletions absl/container/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,7 @@ cc_library(
"//absl/base:endian",
"//absl/base:prefetch",
"//absl/base:raw_logging_internal",
"//absl/functional:function_ref",
"//absl/hash",
"//absl/memory",
"//absl/meta:type_traits",
Expand Down
1 change: 1 addition & 0 deletions absl/container/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@ absl_cc_library(
absl::core_headers
absl::dynamic_annotations
absl::endian
absl::function_ref
absl::hash
absl::hash_function_defaults
absl::hash_policy_traits
Expand Down
72 changes: 60 additions & 12 deletions absl/container/internal/raw_hash_set.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "absl/base/optimization.h"
#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hashtablez_sampler.h"
#include "absl/functional/function_ref.h"
#include "absl/hash/hash.h"
#include "absl/numeric/bits.h"

Expand Down Expand Up @@ -108,6 +109,19 @@ size_t SingleGroupTableH1(size_t hash, ctrl_t* control) {
hash ^ static_cast<size_t>(reinterpret_cast<uintptr_t>(control))));
}

// Returns the address of the slot `i` iterations after `slot` assuming each
// slot has the specified size.
inline void* NextSlot(void* slot, size_t slot_size, size_t i = 1) {
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) +
slot_size * i);
}

// Returns the address of the slot just before `slot` assuming each slot has the
// specified size.
inline void* PrevSlot(void* slot, size_t slot_size) {
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size);
}

} // namespace

GenerationType* EmptyGeneration() {
Expand Down Expand Up @@ -140,6 +154,52 @@ bool ShouldInsertBackwardsForDebug(size_t capacity, size_t hash,
return !is_small(capacity) && (H1(hash, ctrl) ^ RandomSeed()) % 13 > 6;
}

void IterateOverFullSlots(const CommonFields& c, size_t slot_size,
absl::FunctionRef<void(const ctrl_t*, void*)> cb) {
const size_t cap = c.capacity();
const ctrl_t* ctrl = c.control();
void* slot = c.slot_array();
if (is_small(cap)) {
// Mirrored/cloned control bytes in small table are also located in the
// first group (starting from position 0). We are taking group from position
// `capacity` in order to avoid duplicates.

// Small tables capacity fits into portable group, where
// GroupPortableImpl::MaskFull is more efficient for the
// capacity <= GroupPortableImpl::kWidth.
assert(cap <= GroupPortableImpl::kWidth &&
"unexpectedly large small capacity");
static_assert(Group::kWidth >= GroupPortableImpl::kWidth,
"unexpected group width");
// Group starts from kSentinel slot, so indices in the mask will
// be increased by 1.
const auto mask = GroupPortableImpl(ctrl + cap).MaskFull();
--ctrl;
slot = PrevSlot(slot, slot_size);
for (uint32_t i : mask) {
cb(ctrl + i, SlotAddress(slot, i, slot_size));
}
return;
}
size_t remaining = c.size();
ABSL_ATTRIBUTE_UNUSED const size_t original_size_for_assert = remaining;
while (remaining != 0) {
for (uint32_t i : GroupFullEmptyOrDeleted(ctrl).MaskFull()) {
assert(IsFull(ctrl[i]) && "hash table was modified unexpectedly");
cb(ctrl + i, SlotAddress(slot, i, slot_size));
--remaining;
}
ctrl += Group::kWidth;
slot = NextSlot(slot, slot_size, Group::kWidth);
assert((remaining == 0 || *(ctrl - 1) != ctrl_t::kSentinel) &&
"hash table was modified unexpectedly");
}
// NOTE: erasure of the current element is allowed in callback for
// absl::erase_if specialization. So we use `>=`.
assert(original_size_for_assert >= c.size() &&
"hash table was modified unexpectedly");
}

size_t PrepareInsertAfterSoo(size_t hash, size_t slot_size,
CommonFields& common) {
assert(common.capacity() == NextCapacity(SooCapacity()));
Expand Down Expand Up @@ -174,18 +234,6 @@ FindInfo find_first_non_full_outofline(const CommonFields& common,

namespace {

// Returns the address of the slot just after slot assuming each slot has the
// specified size.
static inline void* NextSlot(void* slot, size_t slot_size) {
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) + slot_size);
}

// Returns the address of the slot just before slot assuming each slot has the
// specified size.
static inline void* PrevSlot(void* slot, size_t slot_size) {
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size);
}

// Finds guaranteed to exists empty slot from the given position.
// NOTE: this function is almost never triggered inside of the
// DropDeletesWithoutResize, so we keep it simple.
Expand Down
87 changes: 25 additions & 62 deletions absl/container/internal/raw_hash_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
#include "absl/container/internal/hash_policy_traits.h"
#include "absl/container/internal/hashtable_debug_hooks.h"
#include "absl/container/internal/hashtablez_sampler.h"
#include "absl/functional/function_ref.h"
#include "absl/hash/hash.h"
#include "absl/memory/memory.h"
#include "absl/meta/type_traits.h"
Expand Down Expand Up @@ -1915,56 +1916,12 @@ inline void* SlotAddress(void* slot_array, size_t slot, size_t slot_size) {
(slot * slot_size));
}

// Iterates over all full slots and calls `cb(const ctrl_t*, SlotType*)`.
// No insertion to the table allowed during Callback call.
// Iterates over all full slots and calls `cb(const ctrl_t*, void*)`.
// No insertion to the table is allowed during `cb` call.
// Erasure is allowed only for the element passed to the callback.
template <class SlotType, class Callback>
ABSL_ATTRIBUTE_ALWAYS_INLINE inline void IterateOverFullSlots(
const CommonFields& c, SlotType* slot, Callback cb) {
const size_t cap = c.capacity();
const ctrl_t* ctrl = c.control();
if (is_small(cap)) {
// Mirrored/cloned control bytes in small table are also located in the
// first group (starting from position 0). We are taking group from position
// `capacity` in order to avoid duplicates.

// Small tables capacity fits into portable group, where
// GroupPortableImpl::MaskFull is more efficient for the
// capacity <= GroupPortableImpl::kWidth.
ABSL_SWISSTABLE_ASSERT(cap <= GroupPortableImpl::kWidth &&
"unexpectedly large small capacity");
static_assert(Group::kWidth >= GroupPortableImpl::kWidth,
"unexpected group width");
// Group starts from kSentinel slot, so indices in the mask will
// be increased by 1.
const auto mask = GroupPortableImpl(ctrl + cap).MaskFull();
--ctrl;
--slot;
for (uint32_t i : mask) {
cb(ctrl + i, slot + i);
}
return;
}
size_t remaining = c.size();
ABSL_ATTRIBUTE_UNUSED const size_t original_size_for_assert = remaining;
while (remaining != 0) {
for (uint32_t i : GroupFullEmptyOrDeleted(ctrl).MaskFull()) {
ABSL_SWISSTABLE_ASSERT(IsFull(ctrl[i]) &&
"hash table was modified unexpectedly");
cb(ctrl + i, slot + i);
--remaining;
}
ctrl += Group::kWidth;
slot += Group::kWidth;
ABSL_SWISSTABLE_ASSERT(
(remaining == 0 || *(ctrl - 1) != ctrl_t::kSentinel) &&
"hash table was modified unexpectedly");
}
// NOTE: erasure of the current element is allowed in callback for
// absl::erase_if specialization. So we use `>=`.
ABSL_SWISSTABLE_ASSERT(original_size_for_assert >= c.size() &&
"hash table was modified unexpectedly");
}
// The table must not be in SOO mode.
void IterateOverFullSlots(const CommonFields& c, size_t slot_size,
absl::FunctionRef<void(const ctrl_t*, void*)> cb);

template <typename CharAlloc>
constexpr bool ShouldSampleHashtablezInfoForAlloc() {
Expand Down Expand Up @@ -2956,9 +2913,9 @@ class raw_hash_set {
const size_t shift =
is_single_group(cap) ? (PerTableSalt(control()) | 1) : 0;
IterateOverFullSlots(
that.common(), that.slot_array(),
[&](const ctrl_t* that_ctrl,
slot_type* that_slot) ABSL_ATTRIBUTE_ALWAYS_INLINE {
that.common(), sizeof(slot_type),
[&](const ctrl_t* that_ctrl, void* that_slot_void) {
slot_type* that_slot = static_cast<slot_type*>(that_slot_void);
if (shift == 0) {
// Big tables case. Position must be searched via probing.
// The table is guaranteed to be empty, so we can do faster than
Expand Down Expand Up @@ -3814,10 +3771,10 @@ class raw_hash_set {
void destroy_slots() {
ABSL_SWISSTABLE_ASSERT(!is_soo());
if (PolicyTraits::template destroy_is_trivial<Alloc>()) return;
IterateOverFullSlots(
common(), slot_array(),
[&](const ctrl_t*, slot_type* slot)
ABSL_ATTRIBUTE_ALWAYS_INLINE { this->destroy(slot); });
IterateOverFullSlots(common(), sizeof(slot_type),
[&](const ctrl_t*, void* slot) {
this->destroy(static_cast<slot_type*>(slot));
});
}

void dealloc() {
Expand Down Expand Up @@ -4155,8 +4112,9 @@ class raw_hash_set {
if (empty()) return;

const size_t hash_of_arg = hash_ref()(key);
const auto assert_consistent = [&](const ctrl_t*, slot_type* slot) {
const value_type& element = PolicyTraits::element(slot);
const auto assert_consistent = [&](const ctrl_t*, void* slot) {
const value_type& element =
PolicyTraits::element(static_cast<slot_type*>(slot));
const bool is_key_equal =
PolicyTraits::apply(EqualElement<K>{key, eq_ref()}, element);
if (!is_key_equal) return;
Expand All @@ -4176,7 +4134,7 @@ class raw_hash_set {
}
// We only do validation for small tables so that it's constant time.
if (capacity() > 16) return;
IterateOverFullSlots(common(), slot_array(), assert_consistent);
IterateOverFullSlots(common(), sizeof(slot_type), assert_consistent);
}

// Attempts to find `key` in the table; if it isn't found, returns an iterator
Expand Down Expand Up @@ -4348,8 +4306,11 @@ struct HashtableFreeFunctionsAccess {
}
ABSL_ATTRIBUTE_UNUSED const size_t original_size_for_assert = c->size();
size_t num_deleted = 0;
using SlotType = typename Set::slot_type;
IterateOverFullSlots(
c->common(), c->slot_array(), [&](const ctrl_t* ctrl, auto* slot) {
c->common(), sizeof(SlotType),
[&](const ctrl_t* ctrl, void* slot_void) {
auto* slot = static_cast<SlotType*>(slot_void);
if (pred(Set::PolicyTraits::element(slot))) {
c->destroy(slot);
EraseMetaOnly(c->common(), static_cast<size_t>(ctrl - c->control()),
Expand All @@ -4374,10 +4335,12 @@ struct HashtableFreeFunctionsAccess {
cb(*c->soo_iterator());
return;
}
using SlotType = typename Set::slot_type;
using ElementTypeWithConstness = decltype(*c->begin());
IterateOverFullSlots(
c->common(), c->slot_array(), [&cb](const ctrl_t*, auto* slot) {
ElementTypeWithConstness& element = Set::PolicyTraits::element(slot);
c->common(), sizeof(SlotType), [&cb](const ctrl_t*, void* slot) {
ElementTypeWithConstness& element =
Set::PolicyTraits::element(static_cast<SlotType*>(slot));
cb(element);
});
}
Expand Down
34 changes: 17 additions & 17 deletions absl/container/internal/raw_hash_set_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3498,22 +3498,22 @@ TEST(Table, CountedHash) {
// IterateOverFullSlots doesn't support SOO.
TEST(Table, IterateOverFullSlotsEmpty) {
NonSooIntTable t;
auto fail_if_any = [](const ctrl_t*, auto* i) {
FAIL() << "expected no slots " << **i;
using SlotType = typename NonSooIntTable::slot_type;
auto fail_if_any = [](const ctrl_t*, void* i) {
FAIL() << "expected no slots " << **static_cast<SlotType*>(i);
};
container_internal::IterateOverFullSlots(
RawHashSetTestOnlyAccess::GetCommon(t),
RawHashSetTestOnlyAccess::GetSlots(t), fail_if_any);
RawHashSetTestOnlyAccess::GetCommon(t), sizeof(SlotType), fail_if_any);
for (size_t i = 0; i < 256; ++i) {
t.reserve(i);
container_internal::IterateOverFullSlots(
RawHashSetTestOnlyAccess::GetCommon(t),
RawHashSetTestOnlyAccess::GetSlots(t), fail_if_any);
RawHashSetTestOnlyAccess::GetCommon(t), sizeof(SlotType), fail_if_any);
}
}

TEST(Table, IterateOverFullSlotsFull) {
NonSooIntTable t;
using SlotType = typename NonSooIntTable::slot_type;

std::vector<int64_t> expected_slots;
for (int64_t idx = 0; idx < 128; ++idx) {
Expand All @@ -3522,9 +3522,9 @@ TEST(Table, IterateOverFullSlotsFull) {

std::vector<int64_t> slots;
container_internal::IterateOverFullSlots(
RawHashSetTestOnlyAccess::GetCommon(t),
RawHashSetTestOnlyAccess::GetSlots(t),
[&t, &slots](const ctrl_t* ctrl, auto* i) {
RawHashSetTestOnlyAccess::GetCommon(t), sizeof(SlotType),
[&t, &slots](const ctrl_t* ctrl, void* slot) {
SlotType* i = static_cast<SlotType*>(slot);
ptrdiff_t ctrl_offset =
ctrl - RawHashSetTestOnlyAccess::GetCommon(t).control();
ptrdiff_t slot_offset = i - RawHashSetTestOnlyAccess::GetSlots(t);
Expand All @@ -3543,16 +3543,16 @@ TEST(Table, IterateOverFullSlotsDeathOnRemoval) {
if (reserve_size == -1) reserve_size = size;
for (int64_t idx = 0; idx < size; ++idx) {
NonSooIntTable t;
using SlotType = typename NonSooIntTable::slot_type;
t.reserve(static_cast<size_t>(reserve_size));
for (int val = 0; val <= idx; ++val) {
t.insert(val);
}

container_internal::IterateOverFullSlots(
RawHashSetTestOnlyAccess::GetCommon(t),
RawHashSetTestOnlyAccess::GetSlots(t),
[&t](const ctrl_t*, auto* i) {
int64_t value = **i;
RawHashSetTestOnlyAccess::GetCommon(t), sizeof(SlotType),
[&t](const ctrl_t*, void* slot) {
int64_t value = **static_cast<SlotType*>(slot);
// Erase the other element from 2*k and 2*k+1 pair.
t.erase(value ^ 1);
});
Expand All @@ -3578,16 +3578,16 @@ TEST(Table, IterateOverFullSlotsDeathOnInsert) {
int64_t size = reserve_size / size_divisor;
for (int64_t idx = 1; idx <= size; ++idx) {
NonSooIntTable t;
using SlotType = typename NonSooIntTable::slot_type;
t.reserve(static_cast<size_t>(reserve_size));
for (int val = 1; val <= idx; ++val) {
t.insert(val);
}

container_internal::IterateOverFullSlots(
RawHashSetTestOnlyAccess::GetCommon(t),
RawHashSetTestOnlyAccess::GetSlots(t),
[&t](const ctrl_t*, auto* i) {
int64_t value = **i;
RawHashSetTestOnlyAccess::GetCommon(t), sizeof(SlotType),
[&t](const ctrl_t*, void* slot) {
int64_t value = **static_cast<SlotType*>(slot);
t.insert(-value);
});
}
Expand Down

0 comments on commit 05e72a3

Please sign in to comment.