Skip to content

feat: caching LookupMap implementation #487

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

Merged
merged 80 commits into from
Sep 21, 2021
Merged

feat: caching LookupMap implementation #487

merged 80 commits into from
Sep 21, 2021

Conversation

austinabell
Copy link
Contributor

@austinabell austinabell commented Jul 21, 2021

The interface closely matches a std::collections::HashMap without the functions that require being aware of all keys (as this does not store keys, another data structure will be built on this for that) and adding on functions that are useful in this context such as:

  • set: update value without loading the existing value from state and returning replaced
  • flush: To write cached modifications to storage manually (done automatically on Drop, but this could be useful for if measuring storage usage before and after and not requiring drop)

Copy link
Member

@ChaoticTempest ChaoticTempest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just some comments about naming, but overall looking good

@@ -40,4 +41,12 @@ impl<K, V> StableMap<K, V> {
pub(crate) fn inner(&mut self) -> &mut BTreeMap<K, Box<V>> {
self.map.get_mut()
}
pub(crate) fn with_value<Q: ?Sized, F, T>(&self, k: &Q, f: F) -> Option<T>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should prefix this function with with_ since those have been more primarily associated with the creation of the struct itself. Maybe name it something like map_value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that this is not actually mapping the value since it's just calling a function on the reference to the value, not moving the owned value to be mapped. This might just be semantics, but map would be FnOnce(V) -> T when this is actually FnOnce(&V) -> T

If you feel strongly about this I'm happy to change since I don't really have an opinion here and it's a private API

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh I see. I don't have too strong of an opinion what this should be either. And huh, VaListImpl::with_copy has a similar signature, so it's not too bad for it to have a prefix of with_ but certainly very uncommon

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed to map_value_ref. I don't really think there is a great name, but it's internal and this might be the clearest. Sound good to you?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup that sounds good to me. Thanks

Copy link
Contributor

@matklad matklad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a very quick look, will continue tomorrow!

/// assert_eq!(map["poneyland"], "hoho".to_string());
/// ```
pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
match self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could delegate to or_insert_with_key

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this get compiled away? I just noticed that std::collections::HashMap does not do this and decided to follow their pattern.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, according to my mental model, the compiler should trivially compile that away. This pattern is used by Option and once cell: https://github.com/rust-lang/rust/blob/cdeba02ff71416e014f7130f75166890688be986/library/core/src/option.rs#L1228

It's interesting that maps don't do that. I suppose that's historical -- this is the form the API was added before 1.0, and the form it was copy-pasted into hash brown: rust-lang/rust#22930.

No strong opinion here though -- if you'll feel more confident with the current impl, that's totally fine!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's fine, I'll switch. I'm just noting the reason I had was that I assumed because it was the pattern in std::.::HashMap that there was a benefit

Comment on lines 47 to 50
match self {
Self::Occupied(entry) => entry.into_mut(),
Self::Vacant(entry) => entry.insert(default),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can delegate to or_insert_with

Suggested change
match self {
Self::Occupied(entry) => entry.into_mut(),
Self::Vacant(entry) => entry.insert(default),
}
self.or_insert_with(|| default)

That way, there's a single place which contains the match.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here as above, I'm following std::HashMap pattern. I'll try to dig into comparing what the two compile to exactly to make sure there is no difference

Comment on lines +259 to +260
K: Borrow<Q>,
Q: BorshSerialize + ToOwned<Owned = K> + Ord,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, that's subtle!

I think here we have an extra requrenment that Q: BorshSerialize and K: BorshSerialize use the same serialization format. IE, this extends the usual Borrow contract to cover serialization format as well.

I don't see any immediate problems here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it's not ideal. I couldn't think of a cleaner way to handle this, though. Maybe we can document just to make sure this assumption is clear. Assuming K and Borrow<K> have the same serialization seems in line with how Borrow is defined in some places, but others indicate that it only covers Hash, Eq, Ord

Comment on lines +246 to +247
where
K: Clone,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(blocking for stabilization): the K: Clone bound doesn't make sense -- we already have an owned K here. Can we write

impl StableMap {
  fn get_key_value_mut(&mut self, k: K) -> (&mut K, &mut V) { ... }
}

sounds doable in theory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intuitively, it makes sense, but I don't know how you're getting the &mut K, let alone a &K through the entry API like this.

I'll come back to this to see if there is anything that can be done, but at best with some code duplication all I can come up with is removing one clone, which I'm not certain is very helpful

@austinabell austinabell requested a review from matklad September 20, 2021 16:53
Copy link
Contributor

@matklad matklad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't do a second pass through, but this certainly feel ready as an unstable feature.

@austinabell austinabell merged commit e888599 into master Sep 21, 2021
@austinabell austinabell deleted the austin/hashmap branch September 21, 2021 19:46
This was referenced Sep 22, 2021
chadoh added a commit that referenced this pull request Sep 23, 2021
* master:
  feat: caching LookupMap implementation (#487)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants