@@ -480,6 +480,28 @@ inline size_t NormalizeCapacity(size_t n) {
480
480
: (std::numeric_limits<size_t >::max)() >> LeadingZeros (n);
481
481
}
482
482
483
+ // We use 7/8th as maximum load factor.
484
+ // For 16-wide groups, that gives an average of two empty slots per group.
485
+ inline size_t CapacityToGrowth (size_t capacity) {
486
+ assert (IsValidCapacity (capacity));
487
+ // `capacity*7/8`
488
+ if (Group::kWidth == 8 && capacity == 7 ) {
489
+ // x-x/8 does not work when x==7.
490
+ return 6 ;
491
+ }
492
+ return capacity - capacity / 8 ;
493
+ }
494
+ // From desired "growth" to a lowerbound of the necessary capacity.
495
+ // Might not be a valid one and required NormalizeCapacity().
496
+ inline size_t GrowthToLowerboundCapacity (size_t growth) {
497
+ // `growth*8/7`
498
+ if (Group::kWidth == 8 && growth == 7 ) {
499
+ // x+(x-1)/7 does not work when x==7.
500
+ return 8 ;
501
+ }
502
+ return growth + static_cast <size_t >((static_cast <int64_t >(growth) - 1 ) / 7 );
503
+ }
504
+
483
505
// The node_handle concept from C++17.
484
506
// We specialize node_handle for sets and maps. node_handle_base holds the
485
507
// common API of both.
@@ -819,7 +841,7 @@ class raw_hash_set {
819
841
: ctrl_(EmptyGroup()), settings_(0 , hash, eq, alloc) {
820
842
if (bucket_count) {
821
843
capacity_ = NormalizeCapacity (bucket_count);
822
- growth_left () = static_cast < size_t >(capacity_ * kMaxLoadFactor );
844
+ reset_growth_left ( );
823
845
initialize_slots ();
824
846
}
825
847
}
@@ -1031,7 +1053,7 @@ class raw_hash_set {
1031
1053
}
1032
1054
size_ = 0 ;
1033
1055
reset_ctrl ();
1034
- growth_left () = static_cast < size_t >(capacity_ * kMaxLoadFactor );
1056
+ reset_growth_left ( );
1035
1057
}
1036
1058
assert (empty ());
1037
1059
infoz_.RecordStorageChanged (size_, capacity_);
@@ -1325,16 +1347,16 @@ class raw_hash_set {
1325
1347
infoz_.RecordStorageChanged (size_, capacity_);
1326
1348
return ;
1327
1349
}
1328
- auto m = NormalizeCapacity ((std::max)(n, NumSlotsFast (size ())));
1350
+ // bitor is a faster way of doing `max` here. We will round up to the next
1351
+ // power-of-2-minus-1, so bitor is good enough.
1352
+ auto m = NormalizeCapacity (n | GrowthToLowerboundCapacity (size ()));
1329
1353
// n == 0 unconditionally rehashes as per the standard.
1330
1354
if (n == 0 || m > capacity_) {
1331
1355
resize (m);
1332
1356
}
1333
1357
}
1334
1358
1335
- void reserve (size_t n) {
1336
- rehash (NumSlotsFast (n));
1337
- }
1359
+ void reserve (size_t n) { rehash (GrowthToLowerboundCapacity (n)); }
1338
1360
1339
1361
// Extension API: support for heterogeneous keys.
1340
1362
//
@@ -1512,13 +1534,6 @@ class raw_hash_set {
1512
1534
slot_type&& slot;
1513
1535
};
1514
1536
1515
- // Computes std::ceil(n / kMaxLoadFactor). Faster than calling std::ceil.
1516
- static inline size_t NumSlotsFast (size_t n) {
1517
- return static_cast <size_t >(
1518
- (n * kMaxLoadFactorDenominator + (kMaxLoadFactorNumerator - 1 )) /
1519
- kMaxLoadFactorNumerator );
1520
- }
1521
-
1522
1537
// "erases" the object from the container, except that it doesn't actually
1523
1538
// destroy the object. It only updates all the metadata of the class.
1524
1539
// This can be used in conjunction with Policy::transfer to move the object to
@@ -1556,7 +1571,7 @@ class raw_hash_set {
1556
1571
ctrl_ = reinterpret_cast <ctrl_t *>(layout.template Pointer <0 >(mem));
1557
1572
slots_ = layout.template Pointer <1 >(mem);
1558
1573
reset_ctrl ();
1559
- growth_left () = static_cast < size_t >(capacity_ * kMaxLoadFactor ) - size_ ;
1574
+ reset_growth_left () ;
1560
1575
infoz_.RecordStorageChanged (size_, capacity_);
1561
1576
}
1562
1577
@@ -1662,13 +1677,13 @@ class raw_hash_set {
1662
1677
--i; // repeat
1663
1678
}
1664
1679
}
1665
- growth_left () = static_cast < size_t >(capacity_ * kMaxLoadFactor ) - size_ ;
1680
+ reset_growth_left () ;
1666
1681
}
1667
1682
1668
1683
void rehash_and_grow_if_necessary () {
1669
1684
if (capacity_ == 0 ) {
1670
1685
resize (Group::kWidth - 1 );
1671
- } else if (size () <= kMaxLoadFactor / 2 * capacity_ ) {
1686
+ } else if (size () <= CapacityToGrowth ( capacity ()) / 2 ) {
1672
1687
// Squash DELETED without growing if there is enough capacity.
1673
1688
drop_deletes_without_resize ();
1674
1689
} else {
@@ -1811,6 +1826,10 @@ class raw_hash_set {
1811
1826
SanitizerPoisonMemoryRegion (slots_, sizeof (slot_type) * capacity_);
1812
1827
}
1813
1828
1829
+ void reset_growth_left () {
1830
+ growth_left () = CapacityToGrowth (capacity ()) - size_;
1831
+ }
1832
+
1814
1833
// Sets the control byte, and if `i < Group::kWidth`, set the cloned byte at
1815
1834
// the end too.
1816
1835
void set_ctrl (size_t i, ctrl_t h) {
@@ -1837,12 +1856,6 @@ class raw_hash_set {
1837
1856
return settings_.template get <3 >();
1838
1857
}
1839
1858
1840
- // On average each group has 2 empty slot (for the vectorized case).
1841
- static constexpr int64_t kMaxLoadFactorNumerator = 14 ;
1842
- static constexpr int64_t kMaxLoadFactorDenominator = 16 ;
1843
- static constexpr float kMaxLoadFactor =
1844
- 1.0 * kMaxLoadFactorNumerator / kMaxLoadFactorDenominator ;
1845
-
1846
1859
// TODO(alkis): Investigate removing some of these fields:
1847
1860
// - ctrl/slots can be derived from each other
1848
1861
// - size can be moved into the slot array
@@ -1903,10 +1916,9 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> {
1903
1916
}
1904
1917
1905
1918
static size_t LowerBoundAllocatedByteSize (size_t size) {
1906
- size_t capacity = container_internal::NormalizeCapacity (
1907
- std::ceil (size / Set::kMaxLoadFactor ));
1919
+ size_t capacity = GrowthToLowerboundCapacity (size);
1908
1920
if (capacity == 0 ) return 0 ;
1909
- auto layout = Set::MakeLayout (capacity);
1921
+ auto layout = Set::MakeLayout (NormalizeCapacity ( capacity) );
1910
1922
size_t m = layout.AllocSize ();
1911
1923
size_t per_slot = Traits::space_used (static_cast <const Slot*>(nullptr ));
1912
1924
if (per_slot != ~size_t {}) {
0 commit comments