From a7c3961acb8a2c73cca94e6b1e1a57dde19a9ff2 Mon Sep 17 00:00:00 2001 From: Ulrik Sverdrup Date: Tue, 5 Aug 2025 11:24:11 +0200 Subject: [PATCH 1/2] Pass through u128 hashes to the hashmap Since u128 is already a good hash, use one part of it (lower u64 word) as the hash for the hashmap. --- src/accelerate.rs | 7 ++++--- src/constraint.rs | 6 +++--- src/lib.rs | 1 + src/passthroughhasher.rs | 36 ++++++++++++++++++++++++++++++++++++ src/tree.rs | 5 +++-- 5 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/passthroughhasher.rs diff --git a/src/accelerate.rs b/src/accelerate.rs index e2865fd..7f322c7 100644 --- a/src/accelerate.rs +++ b/src/accelerate.rs @@ -1,8 +1,9 @@ use std::sync::atomic::{AtomicUsize, Ordering}; -use rustc_hash::FxHashMap; use parking_lot::{MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard}; +use crate::passthroughhasher::PassthroughHashMap; + /// The global list of currently alive accelerators. static ACCELERATORS: RwLock<(usize, Vec)> = RwLock::new((0, Vec::new())); @@ -12,7 +13,7 @@ static ID: AtomicUsize = AtomicUsize::new(0); /// The type of each individual accelerator. /// /// Maps from call hashes to return hashes. -type Accelerator = Mutex>; +type Accelerator = Mutex>; /// Generate a new accelerator. pub fn id() -> usize { @@ -58,6 +59,6 @@ pub fn get(id: usize) -> Option> { fn resize(len: usize) { let mut pair = ACCELERATORS.write(); if len > pair.1.len() { - pair.1.resize_with(len, || Mutex::new(FxHashMap::default())); + pair.1.resize_with(len, || Mutex::new(PassthroughHashMap::default())); } } diff --git a/src/constraint.rs b/src/constraint.rs index 6cfe574..73b21a9 100644 --- a/src/constraint.rs +++ b/src/constraint.rs @@ -2,9 +2,9 @@ use std::collections::hash_map::Entry; use std::hash::Hash; use parking_lot::Mutex; -use rustc_hash::FxHashMap; use crate::Track; +use crate::passthroughhasher::PassthroughHashMap; use crate::track::{Call, Sink}; /// Records calls performed on a trackable type. @@ -82,7 +82,7 @@ pub struct CallSequence { /// The raw calls. In order, but deduplicated via the `map`. vec: Vec>, /// A map from hashes of calls to the indices in the vector. - map: FxHashMap, + map: PassthroughHashMap, /// A cursor for iteration in `Self::next`. cursor: usize, } @@ -92,7 +92,7 @@ impl CallSequence { pub fn new() -> Self { Self { vec: Vec::new(), - map: FxHashMap::default(), + map: PassthroughHashMap::default(), cursor: 0, } } diff --git a/src/lib.rs b/src/lib.rs index 90d85cf..f48bce7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,7 @@ mod constraint; mod hash; mod input; mod memoize; +mod passthroughhasher; mod track; mod tree; diff --git a/src/passthroughhasher.rs b/src/passthroughhasher.rs new file mode 100644 index 0000000..b87e524 --- /dev/null +++ b/src/passthroughhasher.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; +use std::hash::{BuildHasher, Hasher}; + +/// Hash Map that re-uses the u128 as the hash value +pub(crate) type PassthroughHashMap = + HashMap; + +#[derive(Copy, Clone, Default)] +pub(crate) struct BuildPassthroughHasher; + +#[derive(Default)] +pub(crate) struct PassthroughHasher { + value: u64, +} + +impl Hasher for PassthroughHasher { + fn finish(&self) -> u64 { + self.value + } + + fn write(&mut self, _bytes: &[u8]) { + unimplemented!("Unsupported operation") + } + + fn write_u128(&mut self, i: u128) { + // truncating conversion + self.value = i as u64; + } +} + +impl BuildHasher for BuildPassthroughHasher { + type Hasher = PassthroughHasher; + fn build_hasher(&self) -> PassthroughHasher { + PassthroughHasher::default() + } +} diff --git a/src/tree.rs b/src/tree.rs index b0c6427..dbcd0ad 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -5,6 +5,7 @@ use rustc_hash::FxHashMap; use slab::Slab; use crate::constraint::CallSequence; +use crate::passthroughhasher::PassthroughHashMap; /// A tree data structure that associates a value with a key hash and a sequence /// of (call, return hash) pairs. @@ -18,7 +19,7 @@ pub struct CallTree { /// Leaf nodes, directly storing outputs. leaves: Slab>, /// The initial node for the given key hash. - start: FxHashMap, + start: PassthroughHashMap, /// Maps from parent nodes to child nodes. The key is a pair of an inner /// node ID and a return hash for that call. The value is the node to /// transition to. @@ -50,8 +51,8 @@ impl CallTree { Self { inner: Slab::new(), leaves: Slab::new(), + start: PassthroughHashMap::default(), edges: FxHashMap::default(), - start: FxHashMap::default(), } } } From c317feb3587ace46eb33fdd7cbd65b08ea87ba87 Mon Sep 17 00:00:00 2001 From: Ulrik Sverdrup Date: Tue, 5 Aug 2025 12:42:37 +0200 Subject: [PATCH 2/2] Add missing inline directives for passthroughhasher --- src/passthroughhasher.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/passthroughhasher.rs b/src/passthroughhasher.rs index b87e524..f18c086 100644 --- a/src/passthroughhasher.rs +++ b/src/passthroughhasher.rs @@ -14,14 +14,17 @@ pub(crate) struct PassthroughHasher { } impl Hasher for PassthroughHasher { + #[inline(always)] fn finish(&self) -> u64 { self.value } + #[inline] fn write(&mut self, _bytes: &[u8]) { unimplemented!("Unsupported operation") } + #[inline] fn write_u128(&mut self, i: u128) { // truncating conversion self.value = i as u64; @@ -30,6 +33,7 @@ impl Hasher for PassthroughHasher { impl BuildHasher for BuildPassthroughHasher { type Hasher = PassthroughHasher; + #[inline] fn build_hasher(&self) -> PassthroughHasher { PassthroughHasher::default() }