-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
[move][stdlib] Add efficient BigOrderedMap implementation #14753
base: igor/ordered_map
Are you sure you want to change the base?
Conversation
⏱️ 1h 25m total CI duration on this PR
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## igor/ordered_map #14753 +/- ##
=================================================
Coverage 57.4% 57.4%
=================================================
Files 859 859
Lines 211663 211663
=================================================
Hits 121527 121527
Misses 90136 90136 ☔ View full report in Codecov by Sentry. |
4ae8d66
to
6e1add6
Compare
3ffc3e4
to
95aaf51
Compare
aptos-move/framework/aptos-stdlib/sources/data_structures/btree_map.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-stdlib/sources/data_structures/btree_map.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-stdlib/sources/data_structures/btree_map.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-stdlib/sources/data_structures/btree_map.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-stdlib/sources/data_structures/slots_storage.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-stdlib/sources/data_structures/slots_storage.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-stdlib/sources/data_structures/slots_storage.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-stdlib/sources/data_structures/slots_storage.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-stdlib/sources/data_structures/slots_storage.move
Outdated
Show resolved
Hide resolved
The world with Enums is beautiful 🚀 |
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.
haven't fully review the tree code with some comments first.
/// Destroys the tree if it's empty, otherwise aborts. | ||
public fun destroy_empty<K: store, V: store>(self: BTreeMap<K, V>) { | ||
let BTreeMap::V1 { nodes, root_index, min_leaf_index: _, max_leaf_index: _, inner_max_degree: _, leaf_max_degree: _ } = self; | ||
aptos_std::debug::print(&nodes); |
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.
nit
aptos-move/framework/aptos-stdlib/sources/data_structures/slots_storage.move
Outdated
Show resolved
Hide resolved
aptos-move/framework/aptos-stdlib/sources/data_structures/slots_storage.move
Outdated
Show resolved
Hide resolved
enum SlotsStorage<T: store> has store { | ||
Simple { | ||
slots: Table<u64, Link<T>>, | ||
new_slot_index: u64, |
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.
why do we need the non-reuse version?
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.
if one app does all inserts and removals, they might want to get the refunds back, instead of worrying about amortization
/// Returns a newly allocated vector containing the elements in the range [at, len). | ||
/// After the call, the original vector will be left containing the elements [0, at) | ||
/// with its previous capacity unchanged. | ||
public fun split_off<Element>(self: &mut vector<Element>, at: u64): vector<Element> { |
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.
This deserves a native functions, so does the next one.
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.
implemented in the stack
@@ -87,7 +87,7 @@ module aptos_std::table { | |||
drop_unchecked_box<K, V, Box<V>>(self) | |||
} | |||
|
|||
public(friend) fun destroy<K: copy + drop, V>(self: Table<K, V>) { | |||
public fun destroy_empty<K: copy + drop, V>(self: Table<K, V>) { |
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.
IIRC, this function does NOT check the Table is empty...
Could we confirm with @wrwg ?
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.
there's no way to check if a table is empty or not, that's why we didn't expose destroy_empty
as a public function
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.
fixed
/// An iterator to iterate all keys in the BTreeMap. | ||
enum Iterator<K> has copy, drop { | ||
End, | ||
Some { |
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.
name: None
and Next
?
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.
It's important it is End.
aptos-move/framework/aptos-stdlib/sources/data_structures/btree_map.move
Outdated
Show resolved
Hide resolved
6e1add6
to
9e1d6af
Compare
95aaf51
to
c540209
Compare
} | ||
|
||
// Returns true iff the iterator is an end iterator. | ||
public fun is_end_iter<K: store, V: store>(_tree: &BTreeMap<K, V>, iter: &Iterator<K>): bool { |
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.
this feels more like inline func than actual func?
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.
cannot be done publicly, as enum variants are not public. also some implementation might actually need to do something here.
@@ -87,7 +87,7 @@ module aptos_std::table { | |||
drop_unchecked_box<K, V, Box<V>>(self) | |||
} | |||
|
|||
public(friend) fun destroy<K: copy + drop, V>(self: Table<K, V>) { | |||
public fun destroy_empty<K: copy + drop, V>(self: Table<K, V>) { |
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.
there's no way to check if a table is empty or not, that's why we didn't expose destroy_empty
as a public function
aptos-move/framework/aptos-stdlib/sources/data_structures/btree_map.move
Outdated
Show resolved
Hide resolved
Inner { | ||
// The max key of its child, or the key of the current node if it is a leaf node. | ||
max_key: K, | ||
// The node index of it's child, or NULL_INDEX if the current node is a leaf node. |
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.
This enum is somehow confusing to me. Could you draw a ascii diagram to give an example?
NULL_INDEX if the current node is a leaf node
If this is a leaf node, it should be Leaf
, right?
public fun new_with_config<K: store, V: store>(inner_max_degree: u16, leaf_max_degree: u16, reuse_slots: bool, num_to_preallocate: u64): BTreeMap<K, V> { | ||
assert!(inner_max_degree == 0 || inner_max_degree >= DEFAULT_INNER_MIN_DEGREE, E_INVALID_PARAMETER); | ||
assert!(leaf_max_degree == 0 || leaf_max_degree >= DEFAULT_LEAF_MIN_DEGREE, E_INVALID_PARAMETER); | ||
let nodes = if (reuse_slots) { |
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.
any reason we don't want to reuse slots?
let new_node_children = children.split_off(target_size - 1); | ||
children.insert(l, child); | ||
new_node_children | ||
} else { | ||
children.insert(l, child); | ||
children.split_off(target_size) |
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.
why do we need two cases? Just
children.insert(l, child);
children.split_off(target_size)
is good. unless it's for perf.
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.
it was for perf. since it is only on rebalancing, cleaned it up.
let BTreeMap::V1 { nodes, root_index, min_leaf_index: _, max_leaf_index: _, inner_max_degree: _, leaf_max_degree: _ } = self; | ||
aptos_std::debug::print(&nodes); | ||
nodes.remove(root_index).destroy_empty_node(); | ||
nodes.destroy_empty(); |
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.
As we discussed, how to gc?
self.nodes.borrow_mut(*prev).next = left_node_index; | ||
}; | ||
|
||
if (!*is_leaf) { |
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.
comments... update the non-leaf children parents pointer to the left node, which was the original node with new index.
let left_node_slot = self.nodes.create_transient_slot(); | ||
let left_node_index = left_node_slot.get_index(); | ||
right_node.next = *next; | ||
*next = node_index; |
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.
please comment
// node_index == right_node_index;
}; | ||
|
||
// # of children in the current node exceeds the threshold, need to split into two nodes. | ||
let (right_node_slot, node) = self.nodes.transiently_remove(node_index); |
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.
why not reuse this node as the left node?
// The brother node has enough elements, borrow an element from the brother node. | ||
brother_size = brother_size - 1; | ||
if (brother_index == next) { | ||
let borrowed_element = brother_children.remove(0); |
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.
we need a native remove too.
}; | ||
let borrowed_max_key = borrowed_element.max_key; | ||
children.push_back(borrowed_element); | ||
current_size = current_size + 1; |
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.
unnecessary? just make current_size - 1
at the following line?
9e1d6af
to
0d2ea8f
Compare
c540209
to
bde797a
Compare
bde797a
to
bd71740
Compare
563d169
to
d58cde2
Compare
bd71740
to
09cdccf
Compare
d849f67
to
db1b82e
Compare
499f622
to
fe2da31
Compare
db1b82e
to
c9b6157
Compare
fe2da31
to
17ccbb2
Compare
c9b6157
to
76e0b76
Compare
17ccbb2
to
e5ee36d
Compare
76e0b76
to
da9d4df
Compare
e5ee36d
to
7799fbf
Compare
da9d4df
to
e621db5
Compare
7799fbf
to
e4ddea5
Compare
e621db5
to
3fab948
Compare
e4ddea5
to
e8a2248
Compare
3fab948
to
33d382b
Compare
e8a2248
to
76262a8
Compare
Description
Having efficient and concurrent large-sized "Map" and "OrderedMap" implementations is useful across variety of needs.
Current SmartTable implementation has various limitations, from it being sequential, to it not having smart ways to deal with collisions. It cannot be improved - as the structs are unmodifiable, and so should be deprecated fully.
In this PR:
How Has This Been Tested?
provided extensive unit tests. will probably consolidate and remove some before committing (to not make CI slower/more expensive), but for development those were useful.
Key Areas to Review
Type of Change
Which Components or Systems Does This Change Impact?
Checklist