Export of internal Abseil changes.
-- 461c1b6eb19490429db3bc6dd10ee32df9429cd7 by Samuel Benzaquen <sbenza@google.com>: Group all the capacity/growth calculation in one place. This helps remove the unnecessary floating point operations. PiperOrigin-RevId: 229928140 GitOrigin-RevId: 461c1b6eb19490429db3bc6dd10ee32df9429cd7 Change-Id: Ib00f85a6033fcd06a1d38a5987670b1524a80f93
This commit is contained in:
		
							parent
							
								
									efccc50260
								
							
						
					
					
						commit
						0b1e6d417b
					
				
					 2 changed files with 66 additions and 25 deletions
				
			
		| 
						 | 
					@ -480,6 +480,28 @@ inline size_t NormalizeCapacity(size_t n) {
 | 
				
			||||||
             : (std::numeric_limits<size_t>::max)() >> LeadingZeros(n);
 | 
					             : (std::numeric_limits<size_t>::max)() >> LeadingZeros(n);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// We use 7/8th as maximum load factor.
 | 
				
			||||||
 | 
					// For 16-wide groups, that gives an average of two empty slots per group.
 | 
				
			||||||
 | 
					inline size_t CapacityToGrowth(size_t capacity) {
 | 
				
			||||||
 | 
					  assert(IsValidCapacity(capacity));
 | 
				
			||||||
 | 
					  // `capacity*7/8`
 | 
				
			||||||
 | 
					  if (Group::kWidth == 8 && capacity == 7) {
 | 
				
			||||||
 | 
					    // x-x/8 does not work when x==7.
 | 
				
			||||||
 | 
					    return 6;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return capacity - capacity / 8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// From desired "growth" to a lowerbound of the necessary capacity.
 | 
				
			||||||
 | 
					// Might not be a valid one and required NormalizeCapacity().
 | 
				
			||||||
 | 
					inline size_t GrowthToLowerboundCapacity(size_t growth) {
 | 
				
			||||||
 | 
					  // `growth*8/7`
 | 
				
			||||||
 | 
					  if (Group::kWidth == 8 && growth == 7) {
 | 
				
			||||||
 | 
					    // x+(x-1)/7 does not work when x==7.
 | 
				
			||||||
 | 
					    return 8;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return growth + static_cast<size_t>((static_cast<int64_t>(growth) - 1) / 7);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The node_handle concept from C++17.
 | 
					// The node_handle concept from C++17.
 | 
				
			||||||
// We specialize node_handle for sets and maps. node_handle_base holds the
 | 
					// We specialize node_handle for sets and maps. node_handle_base holds the
 | 
				
			||||||
// common API of both.
 | 
					// common API of both.
 | 
				
			||||||
| 
						 | 
					@ -819,7 +841,7 @@ class raw_hash_set {
 | 
				
			||||||
      : ctrl_(EmptyGroup()), settings_(0, hash, eq, alloc) {
 | 
					      : ctrl_(EmptyGroup()), settings_(0, hash, eq, alloc) {
 | 
				
			||||||
    if (bucket_count) {
 | 
					    if (bucket_count) {
 | 
				
			||||||
      capacity_ = NormalizeCapacity(bucket_count);
 | 
					      capacity_ = NormalizeCapacity(bucket_count);
 | 
				
			||||||
      growth_left() = static_cast<size_t>(capacity_ * kMaxLoadFactor);
 | 
					      reset_growth_left();
 | 
				
			||||||
      initialize_slots();
 | 
					      initialize_slots();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1031,7 +1053,7 @@ class raw_hash_set {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      size_ = 0;
 | 
					      size_ = 0;
 | 
				
			||||||
      reset_ctrl();
 | 
					      reset_ctrl();
 | 
				
			||||||
      growth_left() = static_cast<size_t>(capacity_ * kMaxLoadFactor);
 | 
					      reset_growth_left();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    assert(empty());
 | 
					    assert(empty());
 | 
				
			||||||
    infoz_.RecordStorageChanged(size_, capacity_);
 | 
					    infoz_.RecordStorageChanged(size_, capacity_);
 | 
				
			||||||
| 
						 | 
					@ -1325,16 +1347,16 @@ class raw_hash_set {
 | 
				
			||||||
      infoz_.RecordStorageChanged(size_, capacity_);
 | 
					      infoz_.RecordStorageChanged(size_, capacity_);
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    auto m = NormalizeCapacity((std::max)(n, NumSlotsFast(size())));
 | 
					    // bitor is a faster way of doing `max` here. We will round up to the next
 | 
				
			||||||
 | 
					    // power-of-2-minus-1, so bitor is good enough.
 | 
				
			||||||
 | 
					    auto m = NormalizeCapacity(n | GrowthToLowerboundCapacity(size()));
 | 
				
			||||||
    // n == 0 unconditionally rehashes as per the standard.
 | 
					    // n == 0 unconditionally rehashes as per the standard.
 | 
				
			||||||
    if (n == 0 || m > capacity_) {
 | 
					    if (n == 0 || m > capacity_) {
 | 
				
			||||||
      resize(m);
 | 
					      resize(m);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void reserve(size_t n) {
 | 
					  void reserve(size_t n) { rehash(GrowthToLowerboundCapacity(n)); }
 | 
				
			||||||
    rehash(NumSlotsFast(n));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Extension API: support for heterogeneous keys.
 | 
					  // Extension API: support for heterogeneous keys.
 | 
				
			||||||
  //
 | 
					  //
 | 
				
			||||||
| 
						 | 
					@ -1512,13 +1534,6 @@ class raw_hash_set {
 | 
				
			||||||
    slot_type&& slot;
 | 
					    slot_type&& slot;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Computes std::ceil(n / kMaxLoadFactor). Faster than calling std::ceil.
 | 
					 | 
				
			||||||
  static inline size_t NumSlotsFast(size_t n) {
 | 
					 | 
				
			||||||
    return static_cast<size_t>(
 | 
					 | 
				
			||||||
        (n * kMaxLoadFactorDenominator + (kMaxLoadFactorNumerator - 1)) /
 | 
					 | 
				
			||||||
        kMaxLoadFactorNumerator);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // "erases" the object from the container, except that it doesn't actually
 | 
					  // "erases" the object from the container, except that it doesn't actually
 | 
				
			||||||
  // destroy the object. It only updates all the metadata of the class.
 | 
					  // destroy the object. It only updates all the metadata of the class.
 | 
				
			||||||
  // This can be used in conjunction with Policy::transfer to move the object to
 | 
					  // This can be used in conjunction with Policy::transfer to move the object to
 | 
				
			||||||
| 
						 | 
					@ -1556,7 +1571,7 @@ class raw_hash_set {
 | 
				
			||||||
    ctrl_ = reinterpret_cast<ctrl_t*>(layout.template Pointer<0>(mem));
 | 
					    ctrl_ = reinterpret_cast<ctrl_t*>(layout.template Pointer<0>(mem));
 | 
				
			||||||
    slots_ = layout.template Pointer<1>(mem);
 | 
					    slots_ = layout.template Pointer<1>(mem);
 | 
				
			||||||
    reset_ctrl();
 | 
					    reset_ctrl();
 | 
				
			||||||
    growth_left() = static_cast<size_t>(capacity_ * kMaxLoadFactor) - size_;
 | 
					    reset_growth_left();
 | 
				
			||||||
    infoz_.RecordStorageChanged(size_, capacity_);
 | 
					    infoz_.RecordStorageChanged(size_, capacity_);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1662,13 +1677,13 @@ class raw_hash_set {
 | 
				
			||||||
        --i;  // repeat
 | 
					        --i;  // repeat
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    growth_left() = static_cast<size_t>(capacity_ * kMaxLoadFactor) - size_;
 | 
					    reset_growth_left();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void rehash_and_grow_if_necessary() {
 | 
					  void rehash_and_grow_if_necessary() {
 | 
				
			||||||
    if (capacity_ == 0) {
 | 
					    if (capacity_ == 0) {
 | 
				
			||||||
      resize(Group::kWidth - 1);
 | 
					      resize(Group::kWidth - 1);
 | 
				
			||||||
    } else if (size() <= kMaxLoadFactor / 2 * capacity_) {
 | 
					    } else if (size() <= CapacityToGrowth(capacity()) / 2) {
 | 
				
			||||||
      // Squash DELETED without growing if there is enough capacity.
 | 
					      // Squash DELETED without growing if there is enough capacity.
 | 
				
			||||||
      drop_deletes_without_resize();
 | 
					      drop_deletes_without_resize();
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
| 
						 | 
					@ -1811,6 +1826,10 @@ class raw_hash_set {
 | 
				
			||||||
    SanitizerPoisonMemoryRegion(slots_, sizeof(slot_type) * capacity_);
 | 
					    SanitizerPoisonMemoryRegion(slots_, sizeof(slot_type) * capacity_);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void reset_growth_left() {
 | 
				
			||||||
 | 
					    growth_left() = CapacityToGrowth(capacity()) - size_;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Sets the control byte, and if `i < Group::kWidth`, set the cloned byte at
 | 
					  // Sets the control byte, and if `i < Group::kWidth`, set the cloned byte at
 | 
				
			||||||
  // the end too.
 | 
					  // the end too.
 | 
				
			||||||
  void set_ctrl(size_t i, ctrl_t h) {
 | 
					  void set_ctrl(size_t i, ctrl_t h) {
 | 
				
			||||||
| 
						 | 
					@ -1837,12 +1856,6 @@ class raw_hash_set {
 | 
				
			||||||
    return settings_.template get<3>();
 | 
					    return settings_.template get<3>();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // On average each group has 2 empty slot (for the vectorized case).
 | 
					 | 
				
			||||||
  static constexpr int64_t kMaxLoadFactorNumerator = 14;
 | 
					 | 
				
			||||||
  static constexpr int64_t kMaxLoadFactorDenominator = 16;
 | 
					 | 
				
			||||||
  static constexpr float kMaxLoadFactor =
 | 
					 | 
				
			||||||
      1.0 * kMaxLoadFactorNumerator / kMaxLoadFactorDenominator;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // TODO(alkis): Investigate removing some of these fields:
 | 
					  // TODO(alkis): Investigate removing some of these fields:
 | 
				
			||||||
  // - ctrl/slots can be derived from each other
 | 
					  // - ctrl/slots can be derived from each other
 | 
				
			||||||
  // - size can be moved into the slot array
 | 
					  // - size can be moved into the slot array
 | 
				
			||||||
| 
						 | 
					@ -1903,10 +1916,9 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static size_t LowerBoundAllocatedByteSize(size_t size) {
 | 
					  static size_t LowerBoundAllocatedByteSize(size_t size) {
 | 
				
			||||||
    size_t capacity = container_internal::NormalizeCapacity(
 | 
					    size_t capacity = GrowthToLowerboundCapacity(size);
 | 
				
			||||||
        std::ceil(size / Set::kMaxLoadFactor));
 | 
					 | 
				
			||||||
    if (capacity == 0) return 0;
 | 
					    if (capacity == 0) return 0;
 | 
				
			||||||
    auto layout = Set::MakeLayout(capacity);
 | 
					    auto layout = Set::MakeLayout(NormalizeCapacity(capacity));
 | 
				
			||||||
    size_t m = layout.AllocSize();
 | 
					    size_t m = layout.AllocSize();
 | 
				
			||||||
    size_t per_slot = Traits::space_used(static_cast<const Slot*>(nullptr));
 | 
					    size_t per_slot = Traits::space_used(static_cast<const Slot*>(nullptr));
 | 
				
			||||||
    if (per_slot != ~size_t{}) {
 | 
					    if (per_slot != ~size_t{}) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,6 +48,8 @@ namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using ::testing::DoubleNear;
 | 
					using ::testing::DoubleNear;
 | 
				
			||||||
using ::testing::ElementsAre;
 | 
					using ::testing::ElementsAre;
 | 
				
			||||||
 | 
					using ::testing::Ge;
 | 
				
			||||||
 | 
					using ::testing::Lt;
 | 
				
			||||||
using ::testing::Optional;
 | 
					using ::testing::Optional;
 | 
				
			||||||
using ::testing::Pair;
 | 
					using ::testing::Pair;
 | 
				
			||||||
using ::testing::UnorderedElementsAre;
 | 
					using ::testing::UnorderedElementsAre;
 | 
				
			||||||
| 
						 | 
					@ -62,6 +64,33 @@ TEST(Util, NormalizeCapacity) {
 | 
				
			||||||
  EXPECT_EQ(kMinCapacity * 2 + 1, NormalizeCapacity(kMinCapacity + 2));
 | 
					  EXPECT_EQ(kMinCapacity * 2 + 1, NormalizeCapacity(kMinCapacity + 2));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST(Util, GrowthAndCapacity) {
 | 
				
			||||||
 | 
					  // Verify that GrowthToCapacity gives the minimum capacity that has enough
 | 
				
			||||||
 | 
					  // growth.
 | 
				
			||||||
 | 
					  for (size_t growth = 0; growth < 10000; ++growth) {
 | 
				
			||||||
 | 
					    SCOPED_TRACE(growth);
 | 
				
			||||||
 | 
					    size_t capacity = NormalizeCapacity(GrowthToLowerboundCapacity(growth));
 | 
				
			||||||
 | 
					    // The capacity is large enough for `growth`
 | 
				
			||||||
 | 
					    EXPECT_THAT(CapacityToGrowth(capacity), Ge(growth));
 | 
				
			||||||
 | 
					    if (growth < Group::kWidth - 1) {
 | 
				
			||||||
 | 
					      // Fits in one group, that is the minimum capacity.
 | 
				
			||||||
 | 
					      EXPECT_EQ(capacity, Group::kWidth - 1);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // There is no smaller capacity that works.
 | 
				
			||||||
 | 
					      EXPECT_THAT(CapacityToGrowth(capacity / 2), Lt(growth));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (size_t capacity = Group::kWidth - 1; capacity < 10000;
 | 
				
			||||||
 | 
					       capacity = 2 * capacity + 1) {
 | 
				
			||||||
 | 
					    SCOPED_TRACE(capacity);
 | 
				
			||||||
 | 
					    size_t growth = CapacityToGrowth(capacity);
 | 
				
			||||||
 | 
					    EXPECT_THAT(growth, Lt(capacity));
 | 
				
			||||||
 | 
					    EXPECT_LE(GrowthToLowerboundCapacity(growth), capacity);
 | 
				
			||||||
 | 
					    EXPECT_EQ(NormalizeCapacity(GrowthToLowerboundCapacity(growth)), capacity);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST(Util, probe_seq) {
 | 
					TEST(Util, probe_seq) {
 | 
				
			||||||
  probe_seq<16> seq(0, 127);
 | 
					  probe_seq<16> seq(0, 127);
 | 
				
			||||||
  auto gen = [&]() {
 | 
					  auto gen = [&]() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue