-
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] Introduce efficient OrderedMap implementation #14872
base: igor/native_compare_move_part
Are you sure you want to change the base?
Conversation
⏱️ 4h 32m total CI duration on this PR
🚨 2 jobs on the last run were significantly faster/slower than expected
|
/// Uses cmp::compare for ordering, which compares primitive types natively, and uses common | ||
/// lexicographical sorting for complex types. | ||
module aptos_std::ordered_map { | ||
// friend aptos_std::big_ordered_map; |
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 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.
will be uncommented in the PR with big_ordered_map
/// The OrderedMap datastructure. | ||
enum OrderedMap<K, V> has drop, copy, store { | ||
/// sorted-vector based implementation of OrderedMap | ||
SortedVectorMap { |
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.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## igor/native_compare #14872 +/- ##
====================================================
Coverage 57.4% 57.4%
====================================================
Files 859 859
Lines 211663 211663
====================================================
Hits 121527 121527
Misses 90136 90136 ☔ View full report in Codecov by Sentry. |
let len = self.entries.length(); | ||
let index = binary_search(key, &self.entries, 0, len); | ||
assert!(index < len, error::invalid_argument(EKEY_NOT_FOUND)); | ||
let Entry { key: old_key, value } = self.entries.remove(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.
vector does not keep the order here? IIRC, it swap
and pop_back
. Did you change that behavior?
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.
remove
keeps the order. swap_remove
doesn't
/// Aborts with EKEY_ALREADY_EXISTS if key already exist. | ||
public fun add<K, V>(self: &mut OrderedMap<K, V>, key: K, value: V) { | ||
let len = self.entries.length(); | ||
let index = binary_search(&key, &self.entries, 0, len); |
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 should introduce iterator to vector too so you don't need to know the length anymore for assertion.
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 seems fine to me
/// Changes the key, with keeping the same value attached to it | ||
/// Aborts with EKEY_NOT_FOUND if `old_key` doesn't exist. | ||
/// Aborts with ENEW_KEY_NOT_IN_ORDER if `new_key` doesn't keep the order `old_key` was in. | ||
public(friend) fun replace_key_inplace<K: drop, V>(self: &mut OrderedMap<K, V>, old_key: &K, new_key: K) { |
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.
just curious, any use case for this
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.
seems not generally useful, but in BTreeMap keys here represent max_key of a child subtree, and so I need to modify it in place.
return; | ||
}; | ||
|
||
// TODO: can be implemented more efficiently, as we know both maps are already sorted. |
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 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.
yeah, but issue is here we have vectors, not lists, and so how to do it in-place, without moving too many elements.
Easiest is to can either create a new vector, populate, and then swap (I'll probably just do that)
if "vector" here had capacity, I could start from the end, without temporary storage, but I don't have a way to create empty values here (i.e. have this work without value being drop+copy)
f(iter.iter_borrow_key(self), iter.iter_borrow(self)); | ||
iter = iter.iter_next(self); | ||
} | ||
// vector::for_each_ref( |
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.
what's wrong here?
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.
entry is not a public type, so when this get's inlined in the caller, it cannot do compile.
basically inline functions need to be implemented only using public API.
I can remove this snippet as well
|
||
// return index containing the key, or insert position. | ||
// I.e. index of first element that has key larger or equal to the passed `key` argument. | ||
fun binary_search<K, V>(key: &K, entries: &vector<Entry<K, V>>, start: u64, end: u64): 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.
Can we make a standard Entry
and implement binary search only once?
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.
I am writing binary search only once.
If you mean for binary search to be reusable, so other's don't need to write it, maybe that should be an inline function in vector
Moving Entry to a standard separate file, makes all field accesses to require getter and setter functions, not sure that's best
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.
amazing work! Thanks @igor-aptos . You are the hero!
b508022
to
18ba714
Compare
563d169
to
d58cde2
Compare
18ba714
to
b0d9226
Compare
d58cde2
to
70b3036
Compare
b0d9226
to
cef8611
Compare
70b3036
to
dc12951
Compare
cef8611
to
df51e9c
Compare
dc12951
to
c86d1c6
Compare
c86d1c6
to
3671695
Compare
fcc2f52
to
4fd450c
Compare
3671695
to
d849f67
Compare
4fd450c
to
3f9d2fb
Compare
d849f67
to
db1b82e
Compare
3f9d2fb
to
d5a9277
Compare
db1b82e
to
c9b6157
Compare
d5a9277
to
41a0b13
Compare
c9b6157
to
76e0b76
Compare
41a0b13
to
36d7638
Compare
76e0b76
to
da9d4df
Compare
36d7638
to
e12056a
Compare
da9d4df
to
e621db5
Compare
12807a4
to
1c15e62
Compare
e621db5
to
3fab948
Compare
1c15e62
to
37384ce
Compare
3fab948
to
33d382b
Compare
3144f47
to
73c4d4d
Compare
33d382b
to
16b6fa3
Compare
3c7a17d
to
7c84b5f
Compare
16b6fa3
to
209a588
Compare
Description
Implement efficient OrderedMap implementation in move-stdlib, to replace/deprecate SimpleMap. It is an in-memory datastructure - i.e. doesn't store anything in separate resources.
Implementation is a SortedVectorMap, but given the efficiency improvements of vector operations with memcpy, it's performance characteristics are extremely good.
Running different sizes tests (these are full results), we get milliseconds needed for 100 inserts and 100 removals on maps of different sizes:
Basically achieving log(n) performance, with very simple implementation.
How Has This Been Tested?
provided unit tests
Key Areas to Review
Type of Change
Which Components or Systems Does This Change Impact?
Checklist