Disallow reentrance removal in `absl::erase_if`.
Predicates should generally have no side effects since we do not guarantee the order or quantity for the calls.
In this change we forbid one specific side effect: modification of the table we are iterating over.
As a positive effect we have performance improvements (less computations and less branches).
```
name old cpu/op new cpu/op delta
BM_EraseIf/num_elements:10/num_erased:0 3.02ns ± 2% 2.79ns ± 3% -7.44% (p=0.000 n=35+37)
BM_EraseIf/num_elements:1000/num_erased:0 2.41ns ± 5% 2.05ns ± 4% -14.88% (p=0.000 n=37+37)
BM_EraseIf/num_elements:10/num_erased:5 4.40ns ± 3% 4.22ns ± 3% -4.19% (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:500 9.16ns ± 4% 9.13ns ± 3% ~ (p=0.307 n=37+37)
BM_EraseIf/num_elements:10/num_erased:10 5.77ns ± 3% 5.50ns ± 4% -4.62% (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:1000 7.84ns ± 3% 7.77ns ± 3% -0.94% (p=0.006 n=37+35)
name old time/op new time/op delta
BM_EraseIf/num_elements:10/num_erased:0 3.02ns ± 2% 2.79ns ± 3% -7.48% (p=0.000 n=35+36)
BM_EraseIf/num_elements:1000/num_erased:0 2.41ns ± 5% 2.05ns ± 4% -14.89% (p=0.000 n=37+37)
BM_EraseIf/num_elements:10/num_erased:5 4.42ns ± 3% 4.23ns ± 3% -4.22% (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:500 9.18ns ± 4% 9.15ns ± 3% ~ (p=0.347 n=37+37)
BM_EraseIf/num_elements:10/num_erased:10 5.79ns ± 3% 5.52ns ± 4% -4.61% (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:1000 7.87ns ± 3% 7.79ns ± 3% -0.95% (p=0.007 n=37+35)
name old INSTRUCTIONS/op new INSTRUCTIONS/op delta
BM_EraseIf/num_elements:10/num_erased:0 14.9 ± 0% 12.9 ± 0% -13.46% (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:0 12.7 ± 0% 10.3 ± 0% -18.76% (p=0.000 n=37+37)
BM_EraseIf/num_elements:10/num_erased:5 30.9 ± 0% 28.9 ± 0% -6.48% (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:500 37.6 ± 0% 35.3 ± 0% -6.33% (p=0.000 n=37+37)
BM_EraseIf/num_elements:10/num_erased:10 46.9 ± 0% 44.9 ± 0% -4.27% (p=0.000 n=37+37)
BM_EraseIf/num_elements:1000/num_erased:1000 62.6 ± 0% 60.2 ± 0% -3.80% (p=0.000 n=37+36)
name old CYCLES/op new CYCLES/op delta
BM_EraseIf/num_elements:10/num_erased:0 4.91 ± 1% 4.11 ± 1% -16.35% (p=0.000 n=36+35)
BM_EraseIf/num_elements:1000/num_erased:0 7.74 ± 2% 6.54 ± 2% -15.54% (p=0.000 n=37+37)
BM_EraseIf/num_elements:10/num_erased:5 9.18 ± 3% 8.45 ± 3% -7.88% (p=0.000 n=37+35)
BM_EraseIf/num_elements:1000/num_erased:500 29.5 ± 1% 29.3 ± 1% -0.82% (p=0.000 n=36+37)
BM_EraseIf/num_elements:10/num_erased:10 13.5 ± 1% 12.6 ± 0% -7.06% (p=0.000 n=33+34)
BM_EraseIf/num_elements:1000/num_erased:1000 25.1 ± 0% 24.9 ± 0% -0.90% (p=0.000 n=37+35)
```
PiperOrigin-RevId: 642318040
Change-Id: I78a4a5a9a5881db0818225f9c7c153c562009f66
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h
index d859872..c63b60e 100644
--- a/absl/container/internal/raw_hash_set.h
+++ b/absl/container/internal/raw_hash_set.h
@@ -1902,6 +1902,9 @@
ctrl += Group::kWidth;
if (kAllowRemoveReentrance && *(ctrl - 1) == ctrl_t::kSentinel) {
break;
+ } else {
+ assert((remaining == 0 || *(ctrl - 1) != ctrl_t::kSentinel) &&
+ "element was erased from hash table unexpectedly");
}
slot += Group::kWidth;
}
@@ -4061,7 +4064,7 @@
return 1;
}
size_t num_deleted = 0;
- IterateOverFullSlots</*kAllowRemoveReentrance=*/true>(
+ IterateOverFullSlots</*kAllowRemoveReentrance=*/false>(
c->common(), c->slot_array(), [&](const ctrl_t* ctrl, auto* slot) {
if (pred(Set::PolicyTraits::element(slot))) {
c->destroy(slot);
diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc
index 9b13701..2a6ee65 100644
--- a/absl/container/internal/raw_hash_set_test.cc
+++ b/absl/container/internal/raw_hash_set_test.cc
@@ -3121,34 +3121,6 @@
}
}
-// Test that we are allowed to erase during the callback in erase_if.
-// TODO(b/345744331): Consider to change behavior to disallow erasure in the
-// callback.
-TYPED_TEST(SooTest, EraseIfReentry) {
- for (int size = 0; size < 100; ++size) {
- SCOPED_TRACE(absl::StrCat(size));
- TypeParam t;
- std::vector<int64_t> expected;
- for (int i = 0; i < size; ++i) {
- t.insert(i);
- if (i % 4 == 1 || i % 4 == 2) {
- expected.push_back(i);
- }
- }
- auto pred = [&](const auto& x) {
- auto value = static_cast<int64_t>(x);
- int64_t group = value / 4;
- t.erase(group * 4);
- if (value % 4 == 3) {
- return true;
- }
- return false;
- };
- absl::container_internal::EraseIf(pred, &t);
- ASSERT_THAT(t, testing::UnorderedElementsAreArray(expected));
- }
-}
-
TEST(Table, EraseBeginEndResetsReservedGrowth) {
bool frozen = false;
BadHashFreezableIntTable t{FreezableAlloc<int64_t>(&frozen)};