-
Notifications
You must be signed in to change notification settings - Fork 146
feat: cacheless hamt iteration #2216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changelog entry? |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -382,6 +382,38 @@ where | |||||
Ok(()) | ||||||
} | ||||||
|
||||||
/// Iterates over each KV in the Hamt and runs a function on the values. This is a | ||||||
/// non-caching version of [`Self::for_each`]. It can potentially be more efficient, especially memory-wise, | ||||||
/// for large HAMTs or when the iteration occurs only once. | ||||||
/// | ||||||
/// # Examples | ||||||
/// | ||||||
/// ``` | ||||||
/// use fvm_ipld_hamt::Hamt; | ||||||
/// | ||||||
/// let store = fvm_ipld_blockstore::MemoryBlockstore::default(); | ||||||
/// | ||||||
/// let mut map: Hamt<_, _, usize> = Hamt::new(store); | ||||||
/// map.set(1, 1).unwrap(); | ||||||
/// map.set(4, 2).unwrap(); | ||||||
/// | ||||||
/// let mut total = 0; | ||||||
/// map.for_each_cacheless(|_, v: &u64| { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The type annotation in the example should be
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
/// total += v; | ||||||
/// Ok(()) | ||||||
/// }).unwrap(); | ||||||
/// assert_eq!(total, 3); | ||||||
/// ``` | ||||||
pub fn for_each_cacheless<F>(&self, mut f: F) -> Result<(), Error> | ||||||
where | ||||||
K: Clone, | ||||||
V: DeserializeOwned + Clone, | ||||||
F: FnMut(&K, &V) -> anyhow::Result<()>, | ||||||
{ | ||||||
self.root | ||||||
.for_each_cacheless(&self.store, &self.conf, &mut f) | ||||||
} | ||||||
|
||||||
/// Iterates over each KV in the Hamt and runs a function on the values. If starting key is | ||||||
/// provided, iteration will start from that key. If max is provided, iteration will stop after | ||||||
/// max number of items have been traversed. The number of items that were traversed is | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,9 +72,9 @@ pub enum Identity {} | |
|
||
#[cfg(feature = "identity")] | ||
impl HashAlgorithm for Identity { | ||
fn hash<X: ?Sized>(key: &X) -> HashedKey | ||
fn hash<X>(key: &X) -> HashedKey | ||
where | ||
X: Hash, | ||
X: Hash + ?Sized, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To fix a clippy warning |
||
{ | ||
let mut ident_hasher = IdentityHasher::default(); | ||
key.hash(&mut ident_hasher); | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -29,6 +29,20 @@ pub(crate) struct Node<K, V, H, Ver = version::V3> { | |||||
hash: PhantomData<H>, | ||||||
} | ||||||
|
||||||
impl<K, V, H, Ver> Clone for Node<K, V, H, Ver> | ||||||
where | ||||||
K: Clone, | ||||||
V: Clone, | ||||||
{ | ||||||
fn clone(&self) -> Self { | ||||||
Self { | ||||||
bitfield: self.bitfield, | ||||||
pointers: self.pointers.clone(), | ||||||
hash: Default::default(), | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
impl<K: PartialEq, V: PartialEq, H, Ver> PartialEq for Node<K, V, H, Ver> { | ||||||
fn eq(&self, other: &Self) -> bool { | ||||||
(self.bitfield == other.bitfield) && (self.pointers == other.pointers) | ||||||
|
@@ -206,6 +220,88 @@ where | |||||
self.pointers.is_empty() | ||||||
} | ||||||
|
||||||
/// Non-caching iteration over the values in the node. | ||||||
pub(super) fn for_each_cacheless<S, F>( | ||||||
&self, | ||||||
bs: &S, | ||||||
conf: &Config, | ||||||
f: &mut F, | ||||||
) -> Result<(), Error> | ||||||
where | ||||||
F: FnMut(&K, &V) -> anyhow::Result<()>, | ||||||
S: Blockstore, | ||||||
K: Clone, | ||||||
V: Clone, | ||||||
{ | ||||||
enum IterItem<'a, T> { | ||||||
Borrowed(&'a T), | ||||||
Owned(T), | ||||||
} | ||||||
|
||||||
enum StackItem<'a, T> { | ||||||
Iter(std::slice::Iter<'a, T>), | ||||||
IntoIter(std::vec::IntoIter<T>), | ||||||
} | ||||||
|
||||||
impl<'a, V> From<std::slice::Iter<'a, V>> for StackItem<'a, V> { | ||||||
fn from(value: std::slice::Iter<'a, V>) -> Self { | ||||||
Self::Iter(value) | ||||||
} | ||||||
} | ||||||
|
||||||
impl<V> From<std::vec::IntoIter<V>> for StackItem<'_, V> { | ||||||
fn from(value: std::vec::IntoIter<V>) -> Self { | ||||||
Self::IntoIter(value) | ||||||
} | ||||||
} | ||||||
|
||||||
impl<'a, V> Iterator for StackItem<'a, V> { | ||||||
type Item = IterItem<'a, V>; | ||||||
|
||||||
fn next(&mut self) -> Option<Self::Item> { | ||||||
match self { | ||||||
Self::Iter(it) => it.next().map(IterItem::Borrowed), | ||||||
Self::IntoIter(it) => it.next().map(IterItem::Owned), | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
let mut stack: Vec<StackItem<_>> = vec![self.pointers.iter().into()]; | ||||||
loop { | ||||||
let Some(pointers) = stack.last_mut() else { | ||||||
return Ok(()); | ||||||
}; | ||||||
let Some(pointer) = pointers.next() else { | ||||||
stack.pop(); | ||||||
continue; | ||||||
}; | ||||||
match pointer { | ||||||
IterItem::Borrowed(Pointer::Link { cid, cache: _ }) => { | ||||||
let node = Node::load(conf, bs, cid, stack.len() as u32)?; | ||||||
stack.push(node.pointers.into_iter().into()) | ||||||
} | ||||||
IterItem::Owned(Pointer::Link { cid, cache: _ }) => { | ||||||
let node = Node::load(conf, bs, &cid, stack.len() as u32)?; | ||||||
stack.push(node.pointers.into_iter().into()) | ||||||
} | ||||||
IterItem::Borrowed(Pointer::Dirty(node)) => stack.push(node.pointers.iter().into()), | ||||||
IterItem::Owned(Pointer::Dirty(node)) => { | ||||||
stack.push(node.pointers.clone().into_iter().into()) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
} | ||||||
IterItem::Borrowed(Pointer::Values(kvs)) => { | ||||||
for kv in kvs.iter() { | ||||||
f(kv.key(), kv.value())?; | ||||||
} | ||||||
} | ||||||
IterItem::Owned(Pointer::Values(kvs)) => { | ||||||
for kv in kvs.iter() { | ||||||
f(kv.key(), kv.value())?; | ||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
/// Search for a key. | ||||||
fn search<Q, S: Blockstore>( | ||||||
&self, | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs?