From 2d29b17012821671301fbada60dda38c6e6b2cdf Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 14:21:30 +0200 Subject: [PATCH 1/5] Add shard-index method --- src/sync.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/sync.rs b/src/sync.rs index df77b40..9a80b63 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -208,6 +208,22 @@ impl< self.shards.iter().map(|s| s.read().hits()).sum() } + #[inline] + pub fn shard_index(&self, key: &Q) -> usize + where + Q: Hash + Equivalent + ?Sized, + { + // When choosing the shard, rotate the hash bits usize::BITS / 2 so that we + // give preference to the bits in the middle of the hash. + // Internally hashbrown uses the lower bits for start of probing + the 7 highest, + // so by picking something else we improve the real entropy available to each hashbrown shard. + (self + .hash_builder + .hash_one(key) + .rotate_right(usize::BITS / 2) + & self.shards_mask) as usize + } + #[inline] fn shard_for( &self, From 5a6423ff28ba90e3e0351f8b0b848fe33c684dc8 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 14:22:19 +0200 Subject: [PATCH 2/5] Simplify signature --- src/sync.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index 9a80b63..db06665 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -209,10 +209,7 @@ impl< } #[inline] - pub fn shard_index(&self, key: &Q) -> usize - where - Q: Hash + Equivalent + ?Sized, - { + pub fn shard_index(&self, key: &Q) -> usize { // When choosing the shard, rotate the hash bits usize::BITS / 2 so that we // give preference to the bits in the middle of the hash. // Internally hashbrown uses the lower bits for start of probing + the 7 highest, From af0576d5963dbf0e8ef015e9c9783fa7f8a3856d Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 14:32:41 +0200 Subject: [PATCH 3/5] More docs --- src/sync.rs | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index db06665..6170efb 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -209,16 +209,34 @@ impl< } #[inline] - pub fn shard_index(&self, key: &Q) -> usize { - // When choosing the shard, rotate the hash bits usize::BITS / 2 so that we + fn compute_shard_index(&self, hash: u64) -> u64 { // give preference to the bits in the middle of the hash. + // When choosing the shard, rotate the hash bits usize::BITS / 2 so that we // Internally hashbrown uses the lower bits for start of probing + the 7 highest, // so by picking something else we improve the real entropy available to each hashbrown shard. - (self - .hash_builder - .hash_one(key) - .rotate_right(usize::BITS / 2) - & self.shards_mask) as usize + hash.rotate_right(usize::BITS / 2) & self.shards_mask + } + + /// Returns the shard index for the given key. + /// + /// The returned index is guaranteed to be in `[0, num_shards())`. + /// + /// # Use cases + /// + /// - **Batching**: group keys by shard index before acquiring shard locks, so + /// each lock is taken only once per batch instead of once per key. + /// + /// # Notes + /// + /// The mapping from key to shard index depends on the [`BuildHasher`] supplied + /// at construction time. If two `Cache` instances are built with different + /// hashers, the same key may map to different shard indices. + /// + /// [`BuildHasher`]: std::hash::BuildHasher + #[inline] + pub fn shard_index(&self, key: &Q) -> usize { + let hash = self.hash_builder.hash_one(key); + self.compute_shard_index(hash) as usize } #[inline] @@ -233,11 +251,7 @@ impl< Q: Hash + Equivalent + ?Sized, { let hash = self.hash_builder.hash_one(key); - // When choosing the shard, rotate the hash bits usize::BITS / 2 so that we - // give preference to the bits in the middle of the hash. - // Internally hashbrown uses the lower bits for start of probing + the 7 highest, - // so by picking something else we improve the real entropy available to each hashbrown shard. - let shard_idx = (hash.rotate_right(usize::BITS / 2) & self.shards_mask) as usize; + let shard_idx = self.compute_shard_index(hash) as usize; self.shards.get(shard_idx).map(|s| (s, hash)) } From ada4d8b7391b6be1193048122e82303560e88615 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla <105630300+fsdvh@users.noreply.github.com> Date: Sun, 12 Apr 2026 21:46:41 +0200 Subject: [PATCH 4/5] Update src/sync.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/sync.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index 6170efb..ab6652b 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -210,10 +210,10 @@ impl< #[inline] fn compute_shard_index(&self, hash: u64) -> u64 { - // give preference to the bits in the middle of the hash. - // When choosing the shard, rotate the hash bits usize::BITS / 2 so that we - // Internally hashbrown uses the lower bits for start of probing + the 7 highest, - // so by picking something else we improve the real entropy available to each hashbrown shard. + // Give preference to the bits in the middle of the hash. When choosing the + // shard, rotate the hash by usize::BITS / 2 so we avoid the lower bits and + // the highest 7 bits that hashbrown uses internally for probing, improving + // the real entropy available to each hashbrown shard. hash.rotate_right(usize::BITS / 2) & self.shards_mask } From 54dc9a12da5face817a2943a790473491406930e Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla <105630300+fsdvh@users.noreply.github.com> Date: Sun, 12 Apr 2026 21:46:48 +0200 Subject: [PATCH 5/5] Update src/sync.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/sync.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sync.rs b/src/sync.rs index ab6652b..52cd3a4 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -234,7 +234,7 @@ impl< /// /// [`BuildHasher`]: std::hash::BuildHasher #[inline] - pub fn shard_index(&self, key: &Q) -> usize { + pub fn shard_index + ?Sized>(&self, key: &Q) -> usize { let hash = self.hash_builder.hash_one(key); self.compute_shard_index(hash) as usize }