aboutsummaryrefslogtreecommitdiff
path: root/absl/container/internal
diff options
context:
space:
mode:
authorVitaly Goldshteyn <goldvitaly@google.com>2024-06-20 13:43:52 -0700
committerCopybara-Service <copybara-worker@google.com>2024-06-20 13:45:02 -0700
commit10ac811f7c3720761c0fa0cd2b3fe92116b89420 (patch)
tree045af5b9491c5ebddf445a5921adaacc99d8d183 /absl/container/internal
parent93763764d7554882db31364d822cb9fb924ca594 (diff)
downloadabseil-10ac811f7c3720761c0fa0cd2b3fe92116b89420.tar.gz
abseil-10ac811f7c3720761c0fa0cd2b3fe92116b89420.tar.bz2
abseil-10ac811f7c3720761c0fa0cd2b3fe92116b89420.zip
Create `absl::container_internal::c_for_each_fast` for SwissTable.
This function is aimed to achieve faster iteration through the entire hash table. It is not ready to be used by the public and stays in `container_internal` namespace. Differences with `absl::c_for_each`: 1. No guarantees on order of iteration. Although for the hash table it is partially not guaranteed already. But we do not even guarantee that it is the same order as in the for loop range. De facto, the order is the same at the moment. 2. No mutating reentrance is allowed. Most notably erasing from the hash_table is not allowed. Based on microbenchmarks, there are following conclusions: 1. c_for_each_fast is clearly faster on big tables with 20-60% speedup. 2. Microbenchmarks show regression on a full small table without any empty slots. We should avoid recommending that for small tables. 3. It seems reasonable to use `c_for_each_fast` in places, where `skip_empty_or_deleted` has significant GCU usage. `skip_empty_or_deleted` usage signals that there are "gaps" between elements, so `c_for_each_fast` should be an improvement. PiperOrigin-RevId: 645142512 Change-Id: I279886b8c8b2545504c2bf7e037d27b2545e044d
Diffstat (limited to 'absl/container/internal')
-rw-r--r--absl/container/internal/raw_hash_set.h27
-rw-r--r--absl/container/internal/raw_hash_set_test.cc52
2 files changed, 79 insertions, 0 deletions
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h
index c63b60e3..a722f522 100644
--- a/absl/container/internal/raw_hash_set.h
+++ b/absl/container/internal/raw_hash_set.h
@@ -4075,6 +4075,23 @@ struct HashtableFreeFunctionsAccess {
});
return num_deleted;
}
+
+ template <class Callback, typename Set>
+ static void ForEach(Callback& cb, Set* c) {
+ if (c->empty()) {
+ return;
+ }
+ if (c->is_soo()) {
+ cb(*c->soo_iterator());
+ return;
+ }
+ using ElementTypeWithConstness = decltype(*c->begin());
+ IterateOverFullSlots</*kAllowRemoveReentrance=*/false>(
+ c->common(), c->slot_array(), [&cb](const ctrl_t*, auto* slot) {
+ ElementTypeWithConstness& element = Set::PolicyTraits::element(slot);
+ cb(element);
+ });
+ }
};
// Erases all elements that satisfy the predicate `pred` from the container `c`.
@@ -4084,6 +4101,16 @@ typename raw_hash_set<P, H, E, A>::size_type EraseIf(
return HashtableFreeFunctionsAccess::EraseIf(pred, c);
}
+// Calls `cb` for all elements in the container `c`.
+template <typename P, typename H, typename E, typename A, typename Callback>
+void ForEach(Callback& cb, raw_hash_set<P, H, E, A>* c) {
+ return HashtableFreeFunctionsAccess::ForEach(cb, c);
+}
+template <typename P, typename H, typename E, typename A, typename Callback>
+void ForEach(Callback& cb, const raw_hash_set<P, H, E, A>* c) {
+ return HashtableFreeFunctionsAccess::ForEach(cb, c);
+}
+
namespace hashtable_debug_internal {
template <typename Set>
struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> {
diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc
index 2a6ee656..673d78a6 100644
--- a/absl/container/internal/raw_hash_set_test.cc
+++ b/absl/container/internal/raw_hash_set_test.cc
@@ -3121,6 +3121,58 @@ TYPED_TEST(SooTest, EraseIfPartial) {
}
}
+TYPED_TEST(SooTest, ForEach) {
+ TypeParam t;
+ std::vector<int64_t> expected;
+ for (int size = 0; size < 100; ++size) {
+ SCOPED_TRACE(size);
+ {
+ SCOPED_TRACE("mutable iteration");
+ std::vector<int64_t> actual;
+ auto f = [&](auto& x) { actual.push_back(static_cast<int64_t>(x)); };
+ absl::container_internal::ForEach(f, &t);
+ ASSERT_THAT(actual, testing::UnorderedElementsAreArray(expected));
+ }
+ {
+ SCOPED_TRACE("const iteration");
+ std::vector<int64_t> actual;
+ auto f = [&](auto& x) {
+ static_assert(
+ std::is_const<std::remove_reference_t<decltype(x)>>::value,
+ "no mutable values should be passed to const ForEach");
+ actual.push_back(static_cast<int64_t>(x));
+ };
+ const auto& ct = t;
+ absl::container_internal::ForEach(f, &ct);
+ ASSERT_THAT(actual, testing::UnorderedElementsAreArray(expected));
+ }
+ t.insert(size);
+ expected.push_back(size);
+ }
+}
+
+TEST(Table, ForEachMutate) {
+ StringTable t;
+ using ValueType = StringTable::value_type;
+ std::vector<ValueType> expected;
+ for (int size = 0; size < 100; ++size) {
+ SCOPED_TRACE(size);
+ std::vector<ValueType> actual;
+ auto f = [&](ValueType& x) {
+ actual.push_back(x);
+ x.second += "a";
+ };
+ absl::container_internal::ForEach(f, &t);
+ ASSERT_THAT(actual, testing::UnorderedElementsAreArray(expected));
+ for (ValueType& v : expected) {
+ v.second += "a";
+ }
+ ASSERT_THAT(t, testing::UnorderedElementsAreArray(expected));
+ t.emplace(std::to_string(size), std::to_string(size));
+ expected.emplace_back(std::to_string(size), std::to_string(size));
+ }
+}
+
TEST(Table, EraseBeginEndResetsReservedGrowth) {
bool frozen = false;
BadHashFreezableIntTable t{FreezableAlloc<int64_t>(&frozen)};