Commit 1d401d9c by Vitaly Goldshteyn Committed by Copybara-Service

Use `IterateOverFullSlots` in `absl::erase_if` for hash table.

`EraseIf` itself is not very hot function, but I want to use that as demonstration of the speed of iteration via `IterateOverFullSlots`.

Motivation:
1. We are still going to save some resources.
2. It is the first step to implement faster `absl::c_for_each` that may give larger benefits. Will require readability considerations.

`BM_EraseIf/num_elements:1000/num_erased:0` is just iteration and it gives 60% speed up.
On smaller tables  removing all elements shows 25% speed up. Note that on small tables erasing is much faster due to cl/592272653.

```
name                                          old cpu/op   new cpu/op   delta
BM_EraseIf/num_elements:10/num_erased:0       3.41ns ± 5%  3.03ns ± 3%  -11.14%  (p=0.000 n=37+35)
BM_EraseIf/num_elements:1000/num_erased:0     6.06ns ± 3%  2.42ns ± 3%  -60.05%  (p=0.000 n=34+37)
BM_EraseIf/num_elements:10/num_erased:5       5.90ns ± 3%  4.44ns ± 4%  -24.88%  (p=0.000 n=36+37)
BM_EraseIf/num_elements:1000/num_erased:500   11.0ns ± 2%   9.2ns ± 2%  -16.60%  (p=0.000 n=35+37)
BM_EraseIf/num_elements:10/num_erased:10      8.03ns ± 3%  5.77ns ± 2%  -28.19%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:1000  9.00ns ± 3%  7.83ns ± 2%  -12.98%  (p=0.000 n=37+37)

name                                          old time/op          new time/op          delta
BM_EraseIf/num_elements:10/num_erased:0       3.42ns ± 5%          3.04ns ± 3%  -11.13%  (p=0.000 n=37+36)
BM_EraseIf/num_elements:1000/num_erased:0     6.07ns ± 3%          2.42ns ± 3%  -60.10%  (p=0.000 n=34+37)
BM_EraseIf/num_elements:10/num_erased:5       5.93ns ± 3%          4.45ns ± 4%  -24.89%  (p=0.000 n=36+37)
BM_EraseIf/num_elements:1000/num_erased:500   11.1ns ± 2%           9.2ns ± 2%  -16.61%  (p=0.000 n=35+37)
BM_EraseIf/num_elements:10/num_erased:10      8.06ns ± 3%          5.79ns ± 2%  -28.19%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:1000  9.03ns ± 3%          7.85ns ± 2%  -12.98%  (p=0.000 n=37+37)

name                                          old INSTRUCTIONS/op  new INSTRUCTIONS/op  delta
BM_EraseIf/num_elements:10/num_erased:0         19.5 ± 1%            14.9 ± 0%  -23.79%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:0       19.9 ± 0%            12.7 ± 0%  -36.20%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:10/num_erased:5         36.9 ± 1%            30.9 ± 0%  -16.47%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:500     44.8 ± 0%            37.6 ± 0%  -16.06%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:10/num_erased:10        53.0 ± 1%            46.9 ± 0%  -11.61%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:1000    69.8 ± 0%            62.6 ± 0%  -10.32%  (p=0.000 n=36+37)

name                                          old CYCLES/op        new CYCLES/op        delta
BM_EraseIf/num_elements:10/num_erased:0         6.10 ± 7%            4.91 ± 1%  -19.49%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:0       19.4 ± 1%             7.7 ± 2%  -60.04%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:10/num_erased:5         13.9 ± 4%             9.2 ± 3%  -33.80%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:500     35.5 ± 0%            29.5 ± 1%  -16.74%  (p=0.000 n=37+37)
BM_EraseIf/num_elements:10/num_erased:10        20.8 ± 5%            13.5 ± 0%  -35.07%  (p=0.000 n=37+30)
BM_EraseIf/num_elements:1000/num_erased:1000    28.9 ± 0%            25.1 ± 0%  -13.06%  (p=0.000 n=37+37)
```

PiperOrigin-RevId: 642016364
Change-Id: I8be6af5916bd45fd110bb0398c3ffe932a6a083f
parent d30298a1
...@@ -709,8 +709,10 @@ cc_test( ...@@ -709,8 +709,10 @@ cc_test(
":hash_policy_testing", ":hash_policy_testing",
":hashtable_debug", ":hashtable_debug",
":hashtablez_sampler", ":hashtablez_sampler",
":node_hash_set",
":raw_hash_set", ":raw_hash_set",
":test_allocator", ":test_allocator",
":test_instance_tracker",
"//absl/base", "//absl/base",
"//absl/base:config", "//absl/base:config",
"//absl/base:core_headers", "//absl/base:core_headers",
......
...@@ -783,10 +783,12 @@ absl_cc_test( ...@@ -783,10 +783,12 @@ absl_cc_test(
absl::hashtablez_sampler absl::hashtablez_sampler
absl::log absl::log
absl::memory absl::memory
absl::node_hash_set
absl::prefetch absl::prefetch
absl::raw_hash_set absl::raw_hash_set
absl::strings absl::strings
absl::test_allocator absl::test_allocator
absl::test_instance_tracker
absl::type_traits absl::type_traits
GTest::gmock_main GTest::gmock_main
) )
......
...@@ -1222,6 +1222,8 @@ class RawHashSetLayout { ...@@ -1222,6 +1222,8 @@ class RawHashSetLayout {
size_t slot_offset_; size_t slot_offset_;
}; };
struct HashtableFreeFunctionsAccess;
// We only allow a maximum of 1 SOO element, which makes the implementation // We only allow a maximum of 1 SOO element, which makes the implementation
// much simpler. Complications with multiple SOO elements include: // much simpler. Complications with multiple SOO elements include:
// - Satisfying the guarantee that erasing one element doesn't invalidate // - Satisfying the guarantee that erasing one element doesn't invalidate
...@@ -1858,8 +1860,9 @@ inline void* SlotAddress(void* slot_array, size_t slot, size_t slot_size) { ...@@ -1858,8 +1860,9 @@ inline void* SlotAddress(void* slot_array, size_t slot, size_t slot_size) {
} }
// Iterates over all full slots and calls `cb(const ctrl_t*, SlotType*)`. // Iterates over all full slots and calls `cb(const ctrl_t*, SlotType*)`.
// NOTE: no erasure from this table allowed during Callback call. // If kAllowRemoveReentrance is false, no erasure from this table allowed during
template <class SlotType, class Callback> // Callback call. This mode is slightly faster.
template <bool kAllowRemoveReentrance, class SlotType, class Callback>
ABSL_ATTRIBUTE_ALWAYS_INLINE inline void IterateOverFullSlots( ABSL_ATTRIBUTE_ALWAYS_INLINE inline void IterateOverFullSlots(
const CommonFields& c, SlotType* slot, Callback cb) { const CommonFields& c, SlotType* slot, Callback cb) {
const size_t cap = c.capacity(); const size_t cap = c.capacity();
...@@ -1882,18 +1885,25 @@ ABSL_ATTRIBUTE_ALWAYS_INLINE inline void IterateOverFullSlots( ...@@ -1882,18 +1885,25 @@ ABSL_ATTRIBUTE_ALWAYS_INLINE inline void IterateOverFullSlots(
--ctrl; --ctrl;
--slot; --slot;
for (uint32_t i : mask) { for (uint32_t i : mask) {
cb(ctrl + i, slot + i); if (!kAllowRemoveReentrance || ABSL_PREDICT_TRUE(IsFull(ctrl[i]))) {
cb(ctrl + i, slot + i);
}
} }
return; return;
} }
size_t remaining = c.size(); size_t remaining = c.size();
while (remaining != 0) { while (remaining != 0) {
for (uint32_t i : GroupFullEmptyOrDeleted(ctrl).MaskFull()) { for (uint32_t i : GroupFullEmptyOrDeleted(ctrl).MaskFull()) {
cb(ctrl + i, slot + i); if (!kAllowRemoveReentrance || ABSL_PREDICT_TRUE(IsFull(ctrl[i]))) {
cb(ctrl + i, slot + i);
}
--remaining; --remaining;
} }
slot += Group::kWidth;
ctrl += Group::kWidth; ctrl += Group::kWidth;
if (kAllowRemoveReentrance && *(ctrl - 1) == ctrl_t::kSentinel) {
break;
}
slot += Group::kWidth;
} }
} }
...@@ -2430,6 +2440,7 @@ class raw_hash_set { ...@@ -2430,6 +2440,7 @@ class raw_hash_set {
class iterator : private HashSetIteratorGenerationInfo { class iterator : private HashSetIteratorGenerationInfo {
friend class raw_hash_set; friend class raw_hash_set;
friend struct HashtableFreeFunctionsAccess;
public: public:
using iterator_category = std::forward_iterator_tag; using iterator_category = std::forward_iterator_tag;
...@@ -2736,7 +2747,7 @@ class raw_hash_set { ...@@ -2736,7 +2747,7 @@ class raw_hash_set {
size_t offset = cap; size_t offset = cap;
const size_t shift = const size_t shift =
is_single_group(cap) ? (PerTableSalt(control()) | 1) : 0; is_single_group(cap) ? (PerTableSalt(control()) | 1) : 0;
IterateOverFullSlots( IterateOverFullSlots</*kAllowRemoveReentrance=*/false>(
that.common(), that.slot_array(), that.common(), that.slot_array(),
[&](const ctrl_t* that_ctrl, [&](const ctrl_t* that_ctrl,
slot_type* that_slot) ABSL_ATTRIBUTE_ALWAYS_INLINE { slot_type* that_slot) ABSL_ATTRIBUTE_ALWAYS_INLINE {
...@@ -3411,6 +3422,8 @@ class raw_hash_set { ...@@ -3411,6 +3422,8 @@ class raw_hash_set {
friend struct absl::container_internal::hashtable_debug_internal:: friend struct absl::container_internal::hashtable_debug_internal::
HashtableDebugAccess; HashtableDebugAccess;
friend struct absl::container_internal::HashtableFreeFunctionsAccess;
struct FindElement { struct FindElement {
template <class K, class... Args> template <class K, class... Args>
const_iterator operator()(const K& key, Args&&...) const { const_iterator operator()(const K& key, Args&&...) const {
...@@ -3521,7 +3534,7 @@ class raw_hash_set { ...@@ -3521,7 +3534,7 @@ class raw_hash_set {
inline void destroy_slots() { inline void destroy_slots() {
assert(!is_soo()); assert(!is_soo());
if (PolicyTraits::template destroy_is_trivial<Alloc>()) return; if (PolicyTraits::template destroy_is_trivial<Alloc>()) return;
IterateOverFullSlots( IterateOverFullSlots</*kAllowRemoveReentrance=*/false>(
common(), slot_array(), common(), slot_array(),
[&](const ctrl_t*, slot_type* slot) [&](const ctrl_t*, slot_type* slot)
ABSL_ATTRIBUTE_ALWAYS_INLINE { this->destroy(slot); }); ABSL_ATTRIBUTE_ALWAYS_INLINE { this->destroy(slot); });
...@@ -3866,7 +3879,8 @@ class raw_hash_set { ...@@ -3866,7 +3879,8 @@ class raw_hash_set {
} }
// We only do validation for small tables so that it's constant time. // We only do validation for small tables so that it's constant time.
if (capacity() > 16) return; if (capacity() > 16) return;
IterateOverFullSlots(common(), slot_array(), assert_consistent); IterateOverFullSlots</*kAllowRemoveReentrance=*/false>(
common(), slot_array(), assert_consistent);
#endif #endif
} }
...@@ -4030,19 +4044,41 @@ class raw_hash_set { ...@@ -4030,19 +4044,41 @@ class raw_hash_set {
key_equal{}, allocator_type{}}; key_equal{}, allocator_type{}};
}; };
// Friend access for free functions in raw_hash_set.h.
struct HashtableFreeFunctionsAccess {
template <class Predicate, typename Set>
static typename Set::size_type EraseIf(Predicate& pred, Set* c) {
if (c->empty()) {
return 0;
}
if (c->is_soo()) {
auto it = c->soo_iterator();
if (!pred(*it)) {
return 0;
}
c->destroy(it.slot());
c->common().set_empty_soo();
return 1;
}
size_t num_deleted = 0;
IterateOverFullSlots</*kAllowRemoveReentrance=*/true>(
c->common(), c->slot_array(), [&](const ctrl_t* ctrl, auto* slot) {
if (pred(Set::PolicyTraits::element(slot))) {
c->destroy(slot);
EraseMetaOnly(c->common(), static_cast<size_t>(ctrl - c->control()),
sizeof(*slot));
++num_deleted;
}
});
return num_deleted;
}
};
// Erases all elements that satisfy the predicate `pred` from the container `c`. // Erases all elements that satisfy the predicate `pred` from the container `c`.
template <typename P, typename H, typename E, typename A, typename Predicate> template <typename P, typename H, typename E, typename A, typename Predicate>
typename raw_hash_set<P, H, E, A>::size_type EraseIf( typename raw_hash_set<P, H, E, A>::size_type EraseIf(
Predicate& pred, raw_hash_set<P, H, E, A>* c) { Predicate& pred, raw_hash_set<P, H, E, A>* c) {
const auto initial_size = c->size(); return HashtableFreeFunctionsAccess::EraseIf(pred, c);
for (auto it = c->begin(), last = c->end(); it != last;) {
if (pred(*it)) {
c->erase(it++);
} else {
++it;
}
}
return initial_size - c->size();
} }
namespace hashtable_debug_internal { namespace hashtable_debug_internal {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment