Skip to main content

sparse_hash_map/
growth_policy.rs

1//! Growth policies that map a hash to a bucket and decide the next table size.
2//!
3//! A policy owns the current bucket count and translates a hash into a bucket
4//! index. Three strategies are provided:
5//!
6//! - [`PowerOfTwo`] keeps the bucket count a power of two and maps with a mask.
7//!   Fast, and the default.
8//! - [`Mod`] grows by a rational factor and maps with a modulo. Useful for
9//!   slower growth.
10//! - [`Prime`] uses a fixed table of primes. It spreads values better when the
11//!   hash function is poor, such as an identity hash of pointers.
12//!
13//! Each policy is constructed from a minimum bucket count. The policy may round
14//! that value up and reports the value it settled on. A request above the
15//! policy maximum returns [`LengthError`].
16
17/// The requested bucket count exceeds what a growth policy can represent.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct LengthError;
20
21impl core::fmt::Display for LengthError {
22    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
23        f.write_str("the hash table exceeds its maximum size")
24    }
25}
26
27impl std::error::Error for LengthError {}
28
29/// Strategy for sizing the bucket array and mapping a hash to a bucket.
30///
31/// A policy is created with [`GrowthPolicy::new`], which returns the policy and
32/// the bucket count it settled on. That count is at least the requested
33/// minimum. [`GrowthPolicy::bucket_for_hash`] and [`GrowthPolicy::clear`] must
34/// not allocate or panic.
35pub trait GrowthPolicy: Clone {
36    /// Build a policy for at least `min_bucket_count` buckets.
37    ///
38    /// Returns the policy and the bucket count it settled on. That count is the
39    /// requested minimum rounded up as the policy requires. Returns
40    /// [`LengthError`] when the request is above [`GrowthPolicy::max_bucket_count`].
41    ///
42    /// When `min_bucket_count` is 0 the settled count is 0 and
43    /// `bucket_for_hash` returns 0 for every hash.
44    fn new(min_bucket_count: usize) -> Result<(Self, usize), LengthError>;
45
46    /// Map `hash` to a bucket in `[0, bucket_count)`.
47    fn bucket_for_hash(&self, hash: usize) -> usize;
48
49    /// The bucket count to use on the next growth.
50    ///
51    /// Returns [`LengthError`] when the table cannot grow further.
52    fn next_bucket_count(&self) -> Result<usize, LengthError>;
53
54    /// The largest bucket count the policy can represent.
55    fn max_bucket_count(&self) -> usize;
56
57    /// Reset the policy to the state of a 0-bucket table.
58    fn clear(&mut self);
59
60    /// Whether this policy keeps the bucket count a power of two.
61    ///
62    /// The engine uses this to pick the mask-based probe path.
63    fn is_power_of_two() -> bool {
64        false
65    }
66}
67
68use crate::util::{is_power_of_two, round_up_to_power_of_two};
69
70/// Grow by a power-of-two factor and map with a mask.
71///
72/// `FACTOR` must be a power of two and at least 2. The default is 2.
73#[derive(Clone)]
74pub struct PowerOfTwo<const FACTOR: usize = 2> {
75    mask: usize,
76}
77
78impl<const FACTOR: usize> GrowthPolicy for PowerOfTwo<FACTOR> {
79    fn new(min_bucket_count: usize) -> Result<(Self, usize), LengthError> {
80        assert!(
81            is_power_of_two(FACTOR) && FACTOR >= 2,
82            "growth factor must be a power of two >= 2"
83        );
84
85        let max = Self::max_bucket_count_static();
86        if min_bucket_count > max {
87            return Err(LengthError);
88        }
89
90        if min_bucket_count > 0 {
91            let rounded = round_up_to_power_of_two(min_bucket_count);
92            Ok((Self { mask: rounded - 1 }, rounded))
93        } else {
94            Ok((Self { mask: 0 }, 0))
95        }
96    }
97
98    #[inline]
99    fn bucket_for_hash(&self, hash: usize) -> usize {
100        hash & self.mask
101    }
102
103    fn next_bucket_count(&self) -> Result<usize, LengthError> {
104        if (self.mask + 1) > Self::max_bucket_count_static() / FACTOR {
105            return Err(LengthError);
106        }
107        Ok((self.mask + 1) * FACTOR)
108    }
109
110    fn max_bucket_count(&self) -> usize {
111        Self::max_bucket_count_static()
112    }
113
114    fn clear(&mut self) {
115        self.mask = 0;
116    }
117
118    fn is_power_of_two() -> bool {
119        true
120    }
121}
122
123impl<const FACTOR: usize> PowerOfTwo<FACTOR> {
124    #[inline]
125    fn max_bucket_count_static() -> usize {
126        // Largest power of two representable in a usize.
127        (usize::MAX / 2) + 1
128    }
129}
130
131/// Grow by the rational factor `NUM / DEN` and map with a modulo.
132///
133/// The factor must be at least 1.1. The default is 3/2.
134#[derive(Clone)]
135pub struct Mod<const NUM: usize = 3, const DEN: usize = 2> {
136    modulo: usize,
137}
138
139impl<const NUM: usize, const DEN: usize> Mod<NUM, DEN> {
140    #[inline]
141    fn factor() -> f64 {
142        NUM as f64 / DEN as f64
143    }
144
145    #[inline]
146    fn max_bucket_count_static() -> usize {
147        (usize::MAX as f64 / Self::factor()) as usize
148    }
149}
150
151impl<const NUM: usize, const DEN: usize> GrowthPolicy for Mod<NUM, DEN> {
152    fn new(min_bucket_count: usize) -> Result<(Self, usize), LengthError> {
153        assert!(Self::factor() >= 1.1, "growth factor should be >= 1.1");
154
155        if min_bucket_count > Self::max_bucket_count_static() {
156            return Err(LengthError);
157        }
158
159        let modulo = if min_bucket_count > 0 {
160            min_bucket_count
161        } else {
162            1
163        };
164        // Mod does not change the requested count. It uses it as the modulus.
165        Ok((Self { modulo }, min_bucket_count))
166    }
167
168    #[inline]
169    fn bucket_for_hash(&self, hash: usize) -> usize {
170        hash % self.modulo
171    }
172
173    fn next_bucket_count(&self) -> Result<usize, LengthError> {
174        let max = Self::max_bucket_count_static();
175        if self.modulo == max {
176            return Err(LengthError);
177        }
178
179        let next = (self.modulo as f64 * Self::factor()).ceil();
180        if !next.is_normal() {
181            return Err(LengthError);
182        }
183
184        if next > max as f64 {
185            Ok(max)
186        } else {
187            Ok(next as usize)
188        }
189    }
190
191    fn max_bucket_count(&self) -> usize {
192        Self::max_bucket_count_static()
193    }
194
195    fn clear(&mut self) {
196        self.modulo = 1;
197    }
198}
199
200/// The prime table used by [`Prime`]. Ascending, 40 entries.
201///
202/// Exposed so callers can reason about the growth sequence. A `Prime` policy
203/// steps through these on each growth.
204pub const PRIMES_TABLE: [usize; 40] = [
205    1, 5, 17, 29, 37, 53, 67, 79, 97, 131, 193, 257, 389, 521, 769, 1031, 1543, 2053, 3079, 6151,
206    12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917,
207    25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741, 3221225473,
208    4294967291,
209];
210
211/// Grow by stepping through a fixed table of primes and map with a modulo.
212///
213/// A modulo by a compile-time-unknown prime is still fast here because the
214/// table is small and the index selects the divisor. This policy spreads values
215/// better than [`PowerOfTwo`] when the hash is poor.
216#[derive(Clone)]
217pub struct Prime {
218    iprime: usize,
219}
220
221impl GrowthPolicy for Prime {
222    fn new(min_bucket_count: usize) -> Result<(Self, usize), LengthError> {
223        // Lower bound: first prime not less than the request.
224        let iprime = PRIMES_TABLE.partition_point(|&p| p < min_bucket_count);
225        if iprime == PRIMES_TABLE.len() {
226            return Err(LengthError);
227        }
228
229        let settled = if min_bucket_count > 0 {
230            PRIMES_TABLE[iprime]
231        } else {
232            0
233        };
234        Ok((Self { iprime }, settled))
235    }
236
237    #[inline]
238    fn bucket_for_hash(&self, hash: usize) -> usize {
239        hash % PRIMES_TABLE[self.iprime]
240    }
241
242    fn next_bucket_count(&self) -> Result<usize, LengthError> {
243        if self.iprime + 1 >= PRIMES_TABLE.len() {
244            return Err(LengthError);
245        }
246        Ok(PRIMES_TABLE[self.iprime + 1])
247    }
248
249    fn max_bucket_count(&self) -> usize {
250        PRIMES_TABLE[PRIMES_TABLE.len() - 1]
251    }
252
253    fn clear(&mut self) {
254        self.iprime = 0;
255    }
256}