From 2bdb8c9df87224838f9c3d90f4ebbc671590797b Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 18 Nov 2024 11:32:16 -0800 Subject: [PATCH] [move][stdlib] Add vector::split_off and vector::remove library methods --- aptos-move/e2e-benchmark/src/main.rs | 69 ++++++ .../framework/aptos-stdlib/doc/big_vector.md | 2 +- .../framework/move-stdlib/doc/features.md | 57 +++++ .../framework/move-stdlib/doc/vector.md | 137 ++++++++++-- .../framework/move-stdlib/doc/vector_ext.md | 201 ++++++++++++++++++ .../move-stdlib/sources/configs/features.move | 8 + .../framework/move-stdlib/sources/mem.move | 1 + .../framework/move-stdlib/sources/vector.move | 111 ++++++++-- .../move-stdlib/tests/vector_tests.move | 35 ++- crates/transaction-generator-lib/src/args.rs | 18 ++ .../src/publishing/module_simple.rs | 73 +++++++ .../src/publishing/raw_module_data.rs | 73 ++++++- .../sources/vector_example.move | 47 ++++ testsuite/single_node_performance.py | 3 + 14 files changed, 791 insertions(+), 44 deletions(-) create mode 100644 aptos-move/framework/move-stdlib/doc/vector_ext.md create mode 100644 testsuite/module-publish/src/packages/framework_usecases/sources/vector_example.move diff --git a/aptos-move/e2e-benchmark/src/main.rs b/aptos-move/e2e-benchmark/src/main.rs index 51b2d00e293fb..ec54c169fd9cb 100644 --- a/aptos-move/e2e-benchmark/src/main.rs +++ b/aptos-move/e2e-benchmark/src/main.rs @@ -108,6 +108,15 @@ FungibleAssetMint 6 0.925 1.001 231.6 IncGlobalMilestoneAggV2 { milestone_every: 1 } 6 0.925 1.001 33.3 IncGlobalMilestoneAggV2 { milestone_every: 2 } 6 0.925 1.001 19.1 EmitEvents { count: 1000 } 6 0.925 1.001 8493.7 +VectorTrimAppend { vec_len: 3000, element_len: 1, index: 0, repeats: 0 } 6 0.925 1.001 22909 +VectorTrimAppend { vec_len: 3000, element_len: 1, index: 100, repeats: 1000 } 6 0.925 1.001 47073 +VectorTrimAppend { vec_len: 3000, element_len: 1, index: 2990, repeats: 1000 } 6 0.925 1.001 34788 +VectorRemoveInsert { vec_len: 3000, element_len: 1, index: 100, repeats: 1000 } 6 0.925 1.001 45273 +VectorRemoveInsert { vec_len: 3000, element_len: 1, index: 2998, repeats: 1000 } 6 0.925 1.001 36859 +VectorRangeMove { vec_len: 3000, element_len: 1, index: 1000, move_len: 500, repeats: 1000 } 6 0.925 1.001 65311 +VectorTrimAppend { vec_len: 100, element_len: 100, index: 0, repeats: 0 } 6 0.925 1.001 875 +VectorTrimAppend { vec_len: 100, element_len: 100, index: 10, repeats: 1000 } 6 0.925 1.001 13366 +VectorRangeMove { vec_len: 100, element_len: 100, index: 50, move_len: 10, repeats: 1000 } 6 0.925 1.001 7098 "; struct CalibrationInfo { @@ -198,6 +207,66 @@ fn main() { EntryPoints::IncGlobalMilestoneAggV2 { milestone_every: 1 }, EntryPoints::IncGlobalMilestoneAggV2 { milestone_every: 2 }, EntryPoints::EmitEvents { count: 1000 }, + // long vectors with small elements + EntryPoints::VectorTrimAppend { + // baseline, only vector creation + vec_len: 3000, + element_len: 1, + index: 0, + repeats: 0, + }, + EntryPoints::VectorTrimAppend { + vec_len: 3000, + element_len: 1, + index: 100, + repeats: 1000, + }, + EntryPoints::VectorTrimAppend { + vec_len: 3000, + element_len: 1, + index: 2990, + repeats: 1000, + }, + EntryPoints::VectorRemoveInsert { + vec_len: 3000, + element_len: 1, + index: 100, + repeats: 1000, + }, + EntryPoints::VectorRemoveInsert { + vec_len: 3000, + element_len: 1, + index: 2998, + repeats: 1000, + }, + EntryPoints::VectorRangeMove { + vec_len: 3000, + element_len: 1, + index: 1000, + move_len: 500, + repeats: 1000, + }, + // vectors with large elements + EntryPoints::VectorTrimAppend { + // baseline, only vector creation + vec_len: 100, + element_len: 100, + index: 0, + repeats: 0, + }, + EntryPoints::VectorTrimAppend { + vec_len: 100, + element_len: 100, + index: 10, + repeats: 1000, + }, + EntryPoints::VectorRangeMove { + vec_len: 100, + element_len: 100, + index: 50, + move_len: 10, + repeats: 1000, + }, ]; let mut failures = Vec::new(); diff --git a/aptos-move/framework/aptos-stdlib/doc/big_vector.md b/aptos-move/framework/aptos-stdlib/doc/big_vector.md index 7d785a5b730a1..da978bc07ff9e 100644 --- a/aptos-move/framework/aptos-stdlib/doc/big_vector.md +++ b/aptos-move/framework/aptos-stdlib/doc/big_vector.md @@ -502,7 +502,7 @@ Aborts if i is out of bounds. if (self.end_index == i) { return last_val }; - // because the lack of mem::swap, here we swap remove the requested value from the bucket + // because the lack of mem::swap, here we swap remove the requested value from the bucket // and append the last_val to the bucket then swap the last bucket val back let bucket = table_with_length::borrow_mut(&mut self.buckets, i / self.bucket_size); let bucket_len = vector::length(bucket); diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md index 3919625f4ff9c..e5604063b6071 100644 --- a/aptos-move/framework/move-stdlib/doc/features.md +++ b/aptos-move/framework/move-stdlib/doc/features.md @@ -133,6 +133,8 @@ return true. - [Function `transaction_simulation_enhancement_enabled`](#0x1_features_transaction_simulation_enhancement_enabled) - [Function `get_collection_owner_feature`](#0x1_features_get_collection_owner_feature) - [Function `is_collection_owner_enabled`](#0x1_features_is_collection_owner_enabled) +- [Function `get_native_memory_operations_feature`](#0x1_features_get_native_memory_operations_feature) +- [Function `is_native_memory_operations_enabled`](#0x1_features_is_native_memory_operations_enabled) - [Function `change_feature_flags`](#0x1_features_change_feature_flags) - [Function `change_feature_flags_internal`](#0x1_features_change_feature_flags_internal) - [Function `change_feature_flags_for_next_epoch`](#0x1_features_change_feature_flags_for_next_epoch) @@ -671,6 +673,15 @@ Lifetime: transient + + + + +
const NATIVE_MEMORY_OPERATIONS: u64 = 80;
+
+ + + Lifetime: transient @@ -3274,6 +3285,52 @@ Deprecated feature + + + + +## Function `get_native_memory_operations_feature` + + + +
public fun get_native_memory_operations_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_native_memory_operations_feature(): u64 { NATIVE_MEMORY_OPERATIONS }
+
+ + + +
+ + + +## Function `is_native_memory_operations_enabled` + + + +
public fun is_native_memory_operations_enabled(): bool
+
+ + + +
+Implementation + + +
public fun is_native_memory_operations_enabled(): bool acquires Features {
+    is_enabled(NATIVE_MEMORY_OPERATIONS)
+}
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/doc/vector.md b/aptos-move/framework/move-stdlib/doc/vector.md index b56160dbaf27e..8e9411e10f129 100644 --- a/aptos-move/framework/move-stdlib/doc/vector.md +++ b/aptos-move/framework/move-stdlib/doc/vector.md @@ -40,6 +40,7 @@ the return on investment didn't seem worth it for these simple functions. - [Function `remove`](#0x1_vector_remove) - [Function `remove_value`](#0x1_vector_remove_value) - [Function `swap_remove`](#0x1_vector_swap_remove) +- [Function `replace`](#0x1_vector_replace) - [Function `for_each`](#0x1_vector_for_each) - [Function `for_each_reverse`](#0x1_vector_for_each_reverse) - [Function `for_each_ref`](#0x1_vector_for_each_ref) @@ -87,7 +88,8 @@ the return on investment didn't seem worth it for these simple functions. - [Function `rotate_slice`](#@Specification_1_rotate_slice) -
+
use 0x1::mem;
+
@@ -146,6 +148,18 @@ The length of the vectors are not equal. + + +Whether to utilize native vector::move_range +Vector module cannot call features module, due to cyclic dependency, +so this is a constant. + + +
const USE_MOVE_RANGE: bool = true;
+
+ + + ## Function `empty` @@ -358,7 +372,7 @@ Move prevents from having two mutable references to the same value, so fro vectors are always distinct. -
public(friend) fun move_range<T>(from: &mut vector<T>, removal_position: u64, length: u64, to: &mut vector<T>, insert_position: u64)
+
public fun move_range<T>(from: &mut vector<T>, removal_position: u64, length: u64, to: &mut vector<T>, insert_position: u64)
 
@@ -367,7 +381,7 @@ vectors are always distinct. Implementation -
native public(friend) fun move_range<T>(
+
native public fun move_range<T>(
     from: &mut vector<T>,
     removal_position: u64,
     length: u64,
@@ -482,8 +496,15 @@ Pushes all of the elements of the other vector into the self<
 
 
 
public fun append<Element>(self: &mut vector<Element>, other: vector<Element>) {
-    reverse(&mut other);
-    reverse_append(self, other);
+    if (USE_MOVE_RANGE) {
+        let self_length = length(self);
+        let other_length = length(&other);
+        move_range(&mut other, 0, other_length, self, self_length);
+        destroy_empty(other);
+    } else {
+        reverse(&mut other);
+        reverse_append(self, other);
+    }
 }
 
@@ -525,7 +546,11 @@ Pushes all of the elements of the other vector into the self< ## Function `trim` -Trim a vector to a smaller size, returning the evicted elements in order +Splits (trims) the collection into two at the given index. +Returns a newly allocated vector containing the elements in the range [new_len, len). +After the call, the original vector will be left containing the elements [0, new_len) +with its previous capacity unchanged. +In many languages this is also called split_off.
public fun trim<Element>(self: &mut vector<Element>, new_len: u64): vector<Element>
@@ -538,9 +563,21 @@ Trim a vector to a smaller size, returning the evicted elements in order
 
 
 
public fun trim<Element>(self: &mut vector<Element>, new_len: u64): vector<Element> {
-    let res = trim_reverse(self, new_len);
-    reverse(&mut res);
-    res
+    let len = length(self);
+    assert!(new_len <= len, EINDEX_OUT_OF_BOUNDS);
+
+    let other = empty();
+    if (USE_MOVE_RANGE) {
+        move_range(self, new_len, len - new_len, &mut other, 0);
+    } else {
+        while (len > new_len) {
+            push_back(&mut other, pop_back(self));
+            len = len - 1;
+        };
+        reverse(&mut other);
+    };
+
+    other
 }
 
@@ -728,10 +765,27 @@ Aborts if out of bounds.
public fun insert<Element>(self: &mut vector<Element>, i: u64, e: Element) {
     let len = length(self);
     assert!(i <= len, EINDEX_OUT_OF_BOUNDS);
-    push_back(self, e);
-    while (i < len) {
-        swap(self, i, len);
-        i = i + 1;
+
+    if (USE_MOVE_RANGE) {
+        if (i + 2 >= len) {
+            // When we are close to the end, it is cheaper to not create
+            // a temporary vector, and swap directly
+            push_back(self, e);
+            while (i < len) {
+                swap(self, i, len);
+                i = i + 1;
+            };
+        } else {
+            let other = singleton(e);
+            move_range(&mut other, 0, 1, self, i);
+            destroy_empty(other);
+        }
+    } else {
+        push_back(self, e);
+        while (i < len) {
+            swap(self, i, len);
+            i = i + 1;
+        };
     };
 }
 
@@ -763,9 +817,25 @@ Aborts if i is out of bounds. // i out of bounds; abort if (i >= len) abort EINDEX_OUT_OF_BOUNDS; - len = len - 1; - while (i < len) swap(self, i, { i = i + 1; i }); - pop_back(self) + if (USE_MOVE_RANGE) { + // When we are close to the end, it is cheaper to not create + // a temporary vector, and swap directly + if (i + 3 >= len) { + len = len - 1; + while (i < len) swap(self, i, { i = i + 1; i }); + pop_back(self) + } else { + let other = empty(); + move_range(self, i, 1, &mut other, 0); + let result = pop_back(&mut other); + destroy_empty(other); + result + } + } else { + len = len - 1; + while (i < len) swap(self, i, { i = i + 1; i }); + pop_back(self) + } }
@@ -838,6 +908,41 @@ Aborts if i is out of bounds. + + + + +## Function `replace` + +Replace the ith element of the vector self with the given value, and return +to the caller the value that was there before. +Aborts if i is out of bounds. + + +
public fun replace<Element>(self: &mut vector<Element>, i: u64, val: Element): Element
+
+ + + +
+Implementation + + +
public fun replace<Element>(self: &mut vector<Element>, i: u64, val: Element): Element {
+    let last_idx = length(self);
+    assert!(i < last_idx, EINDEX_OUT_OF_BOUNDS);
+    if (USE_MOVE_RANGE) {
+        mem::replace(borrow_mut(self, i), val)
+    } else {
+        push_back(self, val);
+        swap(self, i, last_idx);
+        pop_back(self)
+    }
+}
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/doc/vector_ext.md b/aptos-move/framework/move-stdlib/doc/vector_ext.md new file mode 100644 index 0000000000000..1fa207f777813 --- /dev/null +++ b/aptos-move/framework/move-stdlib/doc/vector_ext.md @@ -0,0 +1,201 @@ + + + +# Module `0x1::vector_ext` + + + +- [Constants](#@Constants_0) +- [Function `range_move`](#0x1_vector_ext_range_move) +- [Function `split_off`](#0x1_vector_ext_split_off) +- [Function `append`](#0x1_vector_ext_append) +- [Function `insert`](#0x1_vector_ext_insert) +- [Function `remove`](#0x1_vector_ext_remove) + + +
use 0x1::vector;
+
+ + + + + +## Constants + + + + +The index into the vector is out of bounds + + +
const EINDEX_OUT_OF_BOUNDS: u64 = 131072;
+
+ + + + + +## Function `range_move` + + + +
public fun range_move<T>(from: &mut vector<T>, removal_position: u64, length: u64, to: &mut vector<T>, insert_position: u64)
+
+ + + +
+Implementation + + +
public native fun range_move<T>(from: &mut vector<T>, removal_position: u64, length: u64, to: &mut vector<T>, insert_position: u64);
+
+ + + +
+ + + +## Function `split_off` + +Splits the collection into two at the given index. +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>
+
+ + + +
+Implementation + + +
public fun split_off<Element>(self: &mut vector<Element>, at: u64): vector<Element> {
+    let len = vector::length(self);
+    assert!(at <= len, EINDEX_OUT_OF_BOUNDS);
+
+    let other = vector::empty();
+    range_move(self, at, len - at, &mut other, 0);
+
+    // let other = empty();
+    // while (len > at) {
+    //     push_back(&mut other, pop_back(self));
+    //     len = len - 1;
+    // };
+    // reverse(&mut other);
+    other
+}
+
+ + + +
+ + + +## Function `append` + +Pushes all of the elements of the other vector into the self vector. + + +
public fun append<Element>(self: &mut vector<Element>, other: vector<Element>)
+
+ + + +
+Implementation + + +
public fun append<Element>(self: &mut vector<Element>, other: vector<Element>) {
+    let self_length = self.length();
+    let other_length = other.length();
+    range_move(&mut other, 0, other_length, self, self_length);
+    other.destroy_empty();
+    // reverse(&mut other);
+    // reverse_append(self, other);
+}
+
+ + + +
+ + + +## Function `insert` + + + +
public fun insert<Element>(self: &mut vector<Element>, i: u64, e: Element)
+
+ + + +
+Implementation + + +
public fun insert<Element>(self: &mut vector<Element>, i: u64, e: Element) {
+    let len = self.length();
+    assert!(i <= len, EINDEX_OUT_OF_BOUNDS);
+
+    if (i == len) {
+        self.push_back(e);
+    } else {
+        let other = vector::singleton(e);
+        range_move(&mut other, 0, 1, self, i);
+        other.destroy_empty();
+    }
+}
+
+ + + +
+ + + +## Function `remove` + +Remove the ith element of the vector self, shifting all subsequent elements. +This is O(n) and preserves ordering of elements in the vector. +Aborts if i is out of bounds. + + +
public fun remove<Element>(self: &mut vector<Element>, i: u64): Element
+
+ + + +
+Implementation + + +
public fun remove<Element>(self: &mut vector<Element>, i: u64): Element {
+    let len = self.length();
+    // i out of bounds; abort
+    if (i >= len) abort EINDEX_OUT_OF_BOUNDS;
+
+    if (i + 1 == len) {
+        self.pop_back()
+    } else {
+        let other = vector::empty();
+        range_move(self, i, 1, &mut other, 0);
+        let result = other.pop_back();
+        other.destroy_empty();
+        result
+    }
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index 2bdba4056eaae..2b3a5291c600d 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -607,6 +607,14 @@ module std::features { is_enabled(COLLECTION_OWNER) } + const NATIVE_MEMORY_OPERATIONS: u64 = 80; + + public fun get_native_memory_operations_feature(): u64 { NATIVE_MEMORY_OPERATIONS } + + public fun is_native_memory_operations_enabled(): bool acquires Features { + is_enabled(NATIVE_MEMORY_OPERATIONS) + } + // ============================================================================================ // Feature Flag Implementation diff --git a/aptos-move/framework/move-stdlib/sources/mem.move b/aptos-move/framework/move-stdlib/sources/mem.move index 424766f4a2124..e96db063db6c0 100644 --- a/aptos-move/framework/move-stdlib/sources/mem.move +++ b/aptos-move/framework/move-stdlib/sources/mem.move @@ -2,6 +2,7 @@ module std::mem { // TODO - functions here are `public(friend)` here for one release, // and to be changed to `public` one release later. + friend std::vector; #[test_only] friend std::mem_tests; diff --git a/aptos-move/framework/move-stdlib/sources/vector.move b/aptos-move/framework/move-stdlib/sources/vector.move index 2d67042ef4fef..a4408db3f2a7b 100644 --- a/aptos-move/framework/move-stdlib/sources/vector.move +++ b/aptos-move/framework/move-stdlib/sources/vector.move @@ -9,6 +9,8 @@ /// Move functions here because many have loops, requiring loop invariants to prove, and /// the return on investment didn't seem worth it for these simple functions. module std::vector { + use std::mem; + /// The index into the vector is out of bounds const EINDEX_OUT_OF_BOUNDS: u64 = 0x20000; @@ -24,6 +26,11 @@ module std::vector { /// The range in `slice` is invalid. const EINVALID_SLICE_RANGE: u64 = 0x20004; + /// Whether to utilize native vector::move_range + /// Vector module cannot call features module, due to cyclic dependency, + /// so this is a constant. + const USE_MOVE_RANGE: bool = true; + #[bytecode_instruction] /// Create an empty vector. native public fun empty(): vector; @@ -61,11 +68,6 @@ module std::vector { /// Aborts if `i` or `j` is out of bounds. native public fun swap(self: &mut vector, i: u64, j: u64); - // TODO - function `move_range` here is `public(friend)` for one release, - // and to be changed to `public` one release later. - #[test_only] - friend std::vector_tests; - /// Moves range of elements `[removal_position, removal_position + length)` from vector `from`, /// to vector `to`, inserting them starting at the `insert_position`. /// In the `from` vector, elements after the selected range are moved left to fill the hole @@ -75,7 +77,7 @@ module std::vector { /// elements is kept). /// Move prevents from having two mutable references to the same value, so `from` and `to` /// vectors are always distinct. - native public(friend) fun move_range( + native public fun move_range( from: &mut vector, removal_position: u64, length: u64, @@ -121,8 +123,15 @@ module std::vector { /// Pushes all of the elements of the `other` vector into the `self` vector. public fun append(self: &mut vector, other: vector) { - reverse(&mut other); - reverse_append(self, other); + if (USE_MOVE_RANGE) { + let self_length = length(self); + let other_length = length(&other); + move_range(&mut other, 0, other_length, self, self_length); + destroy_empty(other); + } else { + reverse(&mut other); + reverse_append(self, other); + } } spec append { pragma intrinsic = true; @@ -144,11 +153,27 @@ module std::vector { pragma intrinsic = true; } - /// Trim a vector to a smaller size, returning the evicted elements in order + /// Splits (trims) the collection into two at the given index. + /// Returns a newly allocated vector containing the elements in the range [new_len, len). + /// After the call, the original vector will be left containing the elements [0, new_len) + /// with its previous capacity unchanged. + /// In many languages this is also called `split_off`. public fun trim(self: &mut vector, new_len: u64): vector { - let res = trim_reverse(self, new_len); - reverse(&mut res); - res + let len = length(self); + assert!(new_len <= len, EINDEX_OUT_OF_BOUNDS); + + let other = empty(); + if (USE_MOVE_RANGE) { + move_range(self, new_len, len - new_len, &mut other, 0); + } else { + while (len > new_len) { + push_back(&mut other, pop_back(self)); + len = len - 1; + }; + reverse(&mut other); + }; + + other } spec trim { pragma intrinsic = true; @@ -229,10 +254,27 @@ module std::vector { public fun insert(self: &mut vector, i: u64, e: Element) { let len = length(self); assert!(i <= len, EINDEX_OUT_OF_BOUNDS); - push_back(self, e); - while (i < len) { - swap(self, i, len); - i = i + 1; + + if (USE_MOVE_RANGE) { + if (i + 2 >= len) { + // When we are close to the end, it is cheaper to not create + // a temporary vector, and swap directly + push_back(self, e); + while (i < len) { + swap(self, i, len); + i = i + 1; + }; + } else { + let other = singleton(e); + move_range(&mut other, 0, 1, self, i); + destroy_empty(other); + } + } else { + push_back(self, e); + while (i < len) { + swap(self, i, len); + i = i + 1; + }; }; } spec insert { @@ -247,9 +289,25 @@ module std::vector { // i out of bounds; abort if (i >= len) abort EINDEX_OUT_OF_BOUNDS; - len = len - 1; - while (i < len) swap(self, i, { i = i + 1; i }); - pop_back(self) + if (USE_MOVE_RANGE) { + // When we are close to the end, it is cheaper to not create + // a temporary vector, and swap directly + if (i + 3 >= len) { + len = len - 1; + while (i < len) swap(self, i, { i = i + 1; i }); + pop_back(self) + } else { + let other = empty(); + move_range(self, i, 1, &mut other, 0); + let result = pop_back(&mut other); + destroy_empty(other); + result + } + } else { + len = len - 1; + while (i < len) swap(self, i, { i = i + 1; i }); + pop_back(self) + } } spec remove { pragma intrinsic = true; @@ -288,6 +346,21 @@ module std::vector { pragma intrinsic = true; } + /// Replace the `i`th element of the vector `self` with the given value, and return + /// to the caller the value that was there before. + /// Aborts if `i` is out of bounds. + public fun replace(self: &mut vector, i: u64, val: Element): Element { + let last_idx = length(self); + assert!(i < last_idx, EINDEX_OUT_OF_BOUNDS); + if (USE_MOVE_RANGE) { + mem::replace(borrow_mut(self, i), val) + } else { + push_back(self, val); + swap(self, i, last_idx); + pop_back(self) + } + } + /// Apply the function to each element in the vector, consuming it. public inline fun for_each(self: vector, f: |Element|) { reverse(&mut self); // We need to reverse the vector to consume it efficiently diff --git a/aptos-move/framework/move-stdlib/tests/vector_tests.move b/aptos-move/framework/move-stdlib/tests/vector_tests.move index ce558bc3e9cfc..da2bc17dee7c9 100644 --- a/aptos-move/framework/move-stdlib/tests/vector_tests.move +++ b/aptos-move/framework/move-stdlib/tests/vector_tests.move @@ -105,7 +105,18 @@ module std::vector_tests { let v = vector[1, 2]; assert!(&V::trim(&mut v, 0) == &vector[1, 2], 3); }; + { + let v = vector[1, 2, 3, 4, 5, 6]; + let other = V::trim(&mut v, 4); + assert!(v == vector[1, 2, 3, 4], 4); + assert!(other == vector[5, 6], 5); + + let other_empty = V::trim(&mut v, 4); + assert!(v == vector[1, 2, 3, 4], 6); + assert!(other_empty == vector[], 7); + }; } + #[test] #[expected_failure(abort_code = V::EINDEX_OUT_OF_BOUNDS)] fun test_trim_fail() { @@ -113,6 +124,13 @@ module std::vector_tests { V::trim(&mut v, 2); } + #[test] + #[expected_failure(abort_code = V::EINDEX_OUT_OF_BOUNDS)] + fun test_trim_fail_2() { + let v = vector[1, 2, 3]; + V::trim(&mut v, 4); + } + #[test] #[expected_failure(vector_error, minor_status = 1, location = Self)] fun borrow_out_of_range() { @@ -952,7 +970,7 @@ module std::vector_tests { #[test] fun test_destroy() { let v = vector[MoveOnly {}]; - vector::destroy(v, |m| { let MoveOnly {} = m; }) + V::destroy(v, |m| { let MoveOnly {} = m; }) } #[test] @@ -964,4 +982,19 @@ module std::vector_tests { assert!(&v == &vector[3, 6], 0); assert!(&w == &vector[1, 4, 5, 2], 0); } + + #[test] + #[expected_failure(abort_code = V::EINDEX_OUT_OF_BOUNDS)] + fun test_replace_empty_abort() { + let v = vector[]; + let MoveOnly {} = V::replace(&mut v, 0, MoveOnly {}); + V::destroy_empty(v); + } + + #[test] + fun test_replace() { + let v = vector[1, 2, 3, 4]; + V::replace(&mut v, 1, 17); + assert!(v == vector[1, 17, 3, 4], 0); + } } diff --git a/crates/transaction-generator-lib/src/args.rs b/crates/transaction-generator-lib/src/args.rs index fb0cc7779d427..da2a5e5a5f385 100644 --- a/crates/transaction-generator-lib/src/args.rs +++ b/crates/transaction-generator-lib/src/args.rs @@ -45,6 +45,8 @@ pub enum TransactionTypeArg { CreateObjects100, CreateObjects100WithPayload10k, CreateObjectsConflict100WithPayload10k, + VectorTrimAppendLen3000Size1, + VectorRemoveInsertLen3000Size1, ResourceGroupsGlobalWriteTag1KB, ResourceGroupsGlobalWriteAndReadTag1KB, ResourceGroupsSenderWriteTag1KB, @@ -222,6 +224,22 @@ impl TransactionTypeArg { object_payload_size: 10 * 1024, }) }, + TransactionTypeArg::VectorTrimAppendLen3000Size1 => { + call_custom_module(EntryPoints::VectorTrimAppend { + vec_len: 3000, + element_len: 1, + index: 100, + repeats: 1000, + }) + }, + TransactionTypeArg::VectorRemoveInsertLen3000Size1 => { + call_custom_module(EntryPoints::VectorRemoveInsert { + vec_len: 3000, + element_len: 1, + index: 100, + repeats: 1000, + }) + }, TransactionTypeArg::ResourceGroupsGlobalWriteTag1KB => { call_custom_module(EntryPoints::ResourceGroupsGlobalWriteTag { string_length: 1024, diff --git a/crates/transaction-generator-lib/src/publishing/module_simple.rs b/crates/transaction-generator-lib/src/publishing/module_simple.rs index f1b338bb23a16..de1ed2776c96a 100644 --- a/crates/transaction-generator-lib/src/publishing/module_simple.rs +++ b/crates/transaction-generator-lib/src/publishing/module_simple.rs @@ -229,6 +229,25 @@ pub enum EntryPoints { num_objects: u64, object_payload_size: u64, }, + VectorTrimAppend { + vec_len: u64, + element_len: u64, + index: u64, + repeats: u64, + }, + VectorRemoveInsert { + vec_len: u64, + element_len: u64, + index: u64, + repeats: u64, + }, + VectorRangeMove { + vec_len: u64, + element_len: u64, + index: u64, + move_len: u64, + repeats: u64, + }, /// Initialize Token V1 NFT collection TokenV1InitializeCollection, /// Mint an NFT token. Should be called only after InitializeCollection is called @@ -306,6 +325,9 @@ impl EntryPoints { | EntryPoints::ModifyGlobalBoundedAggV2 { .. } | EntryPoints::CreateObjects { .. } | EntryPoints::CreateObjectsConflict { .. } + | EntryPoints::VectorTrimAppend { .. } + | EntryPoints::VectorRemoveInsert { .. } + | EntryPoints::VectorRangeMove { .. } | EntryPoints::TokenV1InitializeCollection | EntryPoints::TokenV1MintAndStoreNFTParallel | EntryPoints::TokenV1MintAndStoreNFTSequential @@ -365,6 +387,9 @@ impl EntryPoints { EntryPoints::CreateObjects { .. } | EntryPoints::CreateObjectsConflict { .. } => { "objects" }, + EntryPoints::VectorTrimAppend { .. } + | EntryPoints::VectorRemoveInsert { .. } + | EntryPoints::VectorRangeMove { .. } => "vector_example", EntryPoints::TokenV1InitializeCollection | EntryPoints::TokenV1MintAndStoreNFTParallel | EntryPoints::TokenV1MintAndStoreNFTSequential @@ -556,6 +581,51 @@ impl EntryPoints { bcs::to_bytes(other.expect("Must provide other")).unwrap(), ], ), + EntryPoints::VectorTrimAppend { + vec_len, + element_len, + index, + repeats, + } + | EntryPoints::VectorRemoveInsert { + vec_len, + element_len, + index, + repeats, + } => get_payload( + module_id, + ident_str!( + if let EntryPoints::VectorTrimAppend { .. } = self { + "test_trim_append" + } else { + "test_remove_insert" + } + ) + .to_owned(), + vec![ + bcs::to_bytes(vec_len).unwrap(), + bcs::to_bytes(element_len).unwrap(), + bcs::to_bytes(index).unwrap(), + bcs::to_bytes(repeats).unwrap(), + ], + ), + EntryPoints::VectorRangeMove { + vec_len, + element_len, + index, + move_len, + repeats, + } => get_payload( + module_id, + ident_str!("test_middle_range_move").to_owned(), + vec![ + bcs::to_bytes(vec_len).unwrap(), + bcs::to_bytes(element_len).unwrap(), + bcs::to_bytes(index).unwrap(), + bcs::to_bytes(move_len).unwrap(), + bcs::to_bytes(repeats).unwrap(), + ], + ), EntryPoints::TokenV1InitializeCollection => get_payload_void( module_id, ident_str!("token_v1_initialize_collection").to_owned(), @@ -825,6 +895,9 @@ impl EntryPoints { EntryPoints::CreateObjects { .. } | EntryPoints::CreateObjectsConflict { .. } => { AutomaticArgs::Signer }, + EntryPoints::VectorTrimAppend { .. } + | EntryPoints::VectorRemoveInsert { .. } + | EntryPoints::VectorRangeMove { .. } => AutomaticArgs::None, EntryPoints::TokenV1InitializeCollection | EntryPoints::TokenV1MintAndStoreNFTParallel | EntryPoints::TokenV1MintAndStoreNFTSequential diff --git a/crates/transaction-generator-lib/src/publishing/raw_module_data.rs b/crates/transaction-generator-lib/src/publishing/raw_module_data.rs index 195b894efff28..ba9b760d122ae 100644 --- a/crates/transaction-generator-lib/src/publishing/raw_module_data.rs +++ b/crates/transaction-generator-lib/src/publishing/raw_module_data.rs @@ -1000,11 +1000,11 @@ pub static MODULES_SIMPLE: Lazy>> = Lazy::new(|| { vec![ pub static PACKAGE_FRAMEWORK_USECASES_METADATA: Lazy> = Lazy::new(|| { vec![ 17, 70, 114, 97, 109, 101, 119, 111, 114, 107, 85, 115, 101, 99, 97, 115, 101, 115, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 64, 55, 49, 50, 55, 50, 50, 66, 65, - 65, 50, 67, 49, 53, 66, 70, 54, 65, 70, 53, 70, 55, 52, 65, 67, 69, 56, - 51, 68, 53, 66, 66, 54, 66, 67, 56, 56, 49, 69, 66, 67, 68, 66, 66, 57, - 52, 48, 49, 54, 66, 66, 53, 66, 67, 48, 49, 51, 56, 49, 49, 50, 51, 53, - 66, 50, 215, 1, 31, 139, 8, 0, 0, 0, 0, 0, 2, 255, 165, 144, 187, 142, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 64, 66, 67, 69, 48, 54, 67, 49, 52, + 57, 67, 52, 51, 66, 56, 50, 53, 55, 53, 48, 54, 67, 65, 65, 57, 66, 66, + 70, 65, 51, 52, 51, 66, 69, 55, 56, 57, 57, 55, 67, 50, 56, 52, 70, 53, + 49, 56, 67, 70, 65, 55, 66, 57, 49, 54, 67, 50, 53, 69, 55, 56, 68, 55, + 55, 69, 215, 1, 31, 139, 8, 0, 0, 0, 0, 0, 2, 255, 165, 144, 187, 142, 194, 64, 12, 69, 251, 249, 10, 107, 182, 38, 236, 15, 108, 193, 238, 138, 150, 6, 170, 8, 33, 51, 49, 33, 100, 176, 163, 241, 240, 144, 16, 255, 78, 44, 30, 130, 22, 100, 23, 215, 246, 189, 167, 112, 217, 97, 104, 177, 166, 185, 99, 220, 18, 252, @@ -1016,13 +1016,14 @@ pub static PACKAGE_FRAMEWORK_USECASES_METADATA: Lazy> = Lazy::new(|| { 134, 107, 160, 99, 88, 35, 215, 38, 101, 5, 65, 88, 51, 114, 6, 172, 170, 68, 170, 96, 20, 5, 236, 5, 197, 88, 184, 242, 182, 183, 231, 117, 187, 101, 108, 116, 77, 105, 113, 55, 219, 163, 143, 163, 223, 191, 127, 239, 46, 112, 10, 188, 112, 161, - 1, 0, 0, 6, 18, 97, 103, 103, 114, 101, 103, 97, 116, 111, 114, 95, 101, 120, + 1, 0, 0, 7, 18, 97, 103, 103, 114, 101, 103, 97, 116, 111, 114, 95, 101, 120, 97, 109, 112, 108, 101, 0, 0, 0, 12, 99, 111, 105, 110, 95, 101, 120, 97, 109, 112, 108, 101, 0, 0, 0, 22, 102, 117, 110, 103, 105, 98, 108, 101, 95, 97, 115, 115, 101, 116, 95, 101, 120, 97, 109, 112, 108, 101, 0, 0, 0, 7, 111, 98, 106, 101, 99, 116, 115, 0, 0, 0, 23, 114, 101, 115, 111, 117, 114, 99, 101, 95, 103, 114, 111, 117, 112, 115, 95, 101, 120, 97, 109, 112, 108, 101, 0, 0, 0, 8, 116, - 111, 107, 101, 110, 95, 118, 49, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, + 111, 107, 101, 110, 95, 118, 49, 0, 0, 0, 14, 118, 101, 99, 116, 111, 114, 95, + 101, 120, 97, 109, 112, 108, 101, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 14, 65, 112, 116, 111, 115, 70, 114, 97, 109, 101, 119, 111, 114, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -1602,6 +1603,63 @@ pub static MODULE_FRAMEWORK_USECASES_TOKEN_V1: Lazy> = Lazy::new(|| { ] }); +#[rustfmt::skip] +pub static MODULE_FRAMEWORK_USECASES_VECTOR_EXAMPLE: Lazy> = Lazy::new(|| { + vec![ + 161, 28, 235, 11, 7, 0, 0, 10, 8, 1, 0, 4, 3, 4, 66, 4, 70, 12, + 5, 82, 113, 7, 195, 1, 139, 1, 8, 206, 2, 64, 16, 142, 3, 31, 12, 173, + 3, 188, 3, 0, 0, 1, 2, 0, 1, 0, 1, 0, 1, 1, 3, 4, 5, 1, + 0, 1, 0, 4, 7, 8, 0, 1, 1, 5, 9, 8, 1, 0, 1, 0, 6, 11, + 8, 0, 1, 1, 7, 12, 5, 1, 0, 1, 1, 8, 4, 8, 1, 0, 1, 0, + 9, 11, 8, 0, 1, 1, 10, 12, 14, 1, 0, 1, 1, 11, 15, 8, 1, 0, + 1, 1, 2, 3, 3, 5, 3, 6, 3, 8, 3, 9, 3, 2, 3, 3, 1, 10, + 10, 3, 1, 3, 1, 10, 3, 3, 7, 10, 9, 0, 3, 9, 0, 1, 9, 0, + 10, 10, 3, 3, 1, 3, 10, 10, 3, 1, 3, 3, 10, 3, 10, 10, 3, 5, + 3, 3, 3, 3, 3, 0, 5, 7, 10, 9, 0, 3, 3, 7, 10, 9, 0, 3, + 5, 10, 10, 3, 10, 10, 3, 1, 7, 10, 10, 3, 7, 10, 10, 3, 4, 3, + 3, 3, 3, 2, 7, 10, 9, 0, 3, 3, 10, 10, 3, 1, 10, 3, 1, 10, + 9, 0, 2, 7, 10, 9, 0, 10, 9, 0, 3, 10, 10, 3, 1, 10, 10, 3, + 14, 118, 101, 99, 116, 111, 114, 95, 101, 120, 97, 109, 112, 108, 101, 12, 103, 101, + 110, 101, 114, 97, 116, 101, 95, 118, 101, 99, 6, 118, 101, 99, 116, 111, 114, 7, + 114, 101, 112, 108, 97, 99, 101, 22, 116, 101, 115, 116, 95, 109, 105, 100, 100, 108, + 101, 95, 114, 97, 110, 103, 101, 95, 109, 111, 118, 101, 10, 109, 111, 118, 101, 95, + 114, 97, 110, 103, 101, 18, 116, 101, 115, 116, 95, 114, 101, 109, 111, 118, 101, 95, + 105, 110, 115, 101, 114, 116, 6, 114, 101, 109, 111, 118, 101, 6, 105, 110, 115, 101, + 114, 116, 16, 116, 101, 115, 116, 95, 116, 114, 105, 109, 95, 97, 112, 112, 101, 110, + 100, 4, 116, 114, 105, 109, 6, 97, 112, 112, 101, 110, 100, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 171, 205, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 20, 99, 111, 109, 112, 105, 108, 97, 116, 105, 111, 110, 95, + 109, 101, 116, 97, 100, 97, 116, 97, 9, 0, 3, 50, 46, 48, 3, 50, 46, 49, + 0, 0, 0, 0, 6, 63, 64, 2, 0, 0, 0, 0, 0, 0, 0, 0, 12, 2, + 6, 0, 0, 0, 0, 0, 0, 0, 0, 12, 3, 9, 12, 4, 5, 8, 5, 23, + 10, 4, 4, 60, 11, 3, 6, 1, 0, 0, 0, 0, 0, 0, 0, 22, 12, 3, + 10, 3, 10, 1, 35, 3, 19, 5, 23, 13, 2, 10, 3, 68, 2, 5, 6, 64, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 12, 6, 6, 0, 0, 0, 0, 0, 0, + 0, 0, 12, 5, 9, 12, 7, 11, 0, 12, 8, 5, 33, 5, 55, 10, 7, 4, + 57, 11, 5, 6, 1, 0, 0, 0, 0, 0, 0, 0, 22, 12, 5, 10, 5, 10, + 8, 35, 3, 44, 5, 55, 10, 2, 12, 10, 13, 10, 6, 0, 0, 0, 0, 0, + 0, 0, 0, 10, 5, 56, 0, 1, 13, 6, 11, 10, 68, 3, 5, 31, 11, 6, + 2, 8, 12, 7, 5, 39, 8, 12, 4, 5, 14, 2, 1, 4, 0, 10, 46, 10, + 0, 10, 1, 17, 0, 12, 5, 11, 0, 11, 1, 17, 0, 12, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 12, 0, 9, 12, 7, 5, 14, 5, 42, 10, 7, 4, + 43, 11, 0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 22, 12, 0, 10, 0, 10, + 4, 35, 3, 25, 5, 42, 13, 5, 13, 6, 12, 8, 10, 2, 10, 3, 11, 8, + 10, 2, 56, 1, 13, 6, 13, 5, 12, 8, 10, 2, 10, 3, 11, 8, 10, 2, + 56, 1, 5, 12, 2, 8, 12, 7, 5, 20, 4, 1, 4, 0, 13, 34, 11, 0, + 11, 1, 17, 0, 12, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 9, + 12, 5, 5, 10, 5, 30, 10, 5, 4, 31, 11, 0, 6, 1, 0, 0, 0, 0, + 0, 0, 0, 22, 12, 0, 10, 0, 10, 3, 35, 3, 21, 5, 30, 13, 4, 10, + 2, 56, 2, 12, 6, 13, 4, 10, 2, 11, 6, 56, 3, 5, 8, 2, 8, 12, + 5, 5, 16, 7, 1, 4, 0, 16, 33, 11, 0, 11, 1, 17, 0, 12, 4, 6, + 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 9, 12, 5, 5, 10, 5, 29, 10, + 5, 4, 30, 11, 0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 22, 12, 0, 10, + 0, 10, 3, 35, 3, 21, 5, 29, 13, 4, 10, 2, 56, 4, 12, 6, 13, 4, + 11, 6, 56, 5, 5, 8, 2, 8, 12, 5, 5, 16, 0, + ] +}); + #[rustfmt::skip] pub static MODULES_FRAMEWORK_USECASES: Lazy>> = Lazy::new(|| { vec![ MODULE_FRAMEWORK_USECASES_AGGREGATOR_EXAMPLE.to_vec(), @@ -1610,6 +1668,7 @@ pub static MODULES_FRAMEWORK_USECASES: Lazy>> = Lazy::new(|| { vec![ MODULE_FRAMEWORK_USECASES_OBJECTS.to_vec(), MODULE_FRAMEWORK_USECASES_RESOURCE_GROUPS_EXAMPLE.to_vec(), MODULE_FRAMEWORK_USECASES_TOKEN_V1.to_vec(), + MODULE_FRAMEWORK_USECASES_VECTOR_EXAMPLE.to_vec(), ]}); #[rustfmt::skip] pub static PACKAGE_AMBASSADOR_TOKEN_METADATA: Lazy> = Lazy::new(|| { diff --git a/testsuite/module-publish/src/packages/framework_usecases/sources/vector_example.move b/testsuite/module-publish/src/packages/framework_usecases/sources/vector_example.move new file mode 100644 index 0000000000000..81fc72256c09a --- /dev/null +++ b/testsuite/module-publish/src/packages/framework_usecases/sources/vector_example.move @@ -0,0 +1,47 @@ + +/// test speed of vector operations +module 0xABCD::vector_example { + use std::vector; + + fun generate_vec(vec_len: u64, element_len: u64): vector> { + let elem = vector::empty(); + for (i in 0..element_len) { + vector::push_back(&mut elem, i); + }; + let vec = vector::empty(); + for (i in 0..vec_len) { + let cur = elem; + vector::replace(&mut cur, 0, i); + vector::push_back(&mut vec, cur); + }; + vec + } + + public entry fun test_trim_append(vec_len: u64, element_len: u64, index: u64, repeats: u64) { + let vec = generate_vec(vec_len, element_len); + + for (i in 0..repeats) { + let part = vector::trim(&mut vec, index); + vector::append(&mut vec, part); + }; + } + + public entry fun test_remove_insert(vec_len: u64, element_len: u64, index: u64, repeats: u64) { + let vec = generate_vec(vec_len, element_len); + + for (i in 0..repeats) { + let part = vector::remove(&mut vec, index); + vector::insert(&mut vec, index, part); + }; + } + + public entry fun test_middle_range_move(vec_len: u64, element_len: u64, index: u64, move_len: u64, repeats: u64) { + let vec1 = generate_vec(vec_len, element_len); + let vec2 = generate_vec(vec_len, element_len); + + for (i in 0..repeats) { + vector::move_range(&mut vec1, index, move_len, &mut vec2, index); + vector::move_range(&mut vec2, index, move_len, &mut vec1, index); + }; + } +} diff --git a/testsuite/single_node_performance.py b/testsuite/single_node_performance.py index 7cfdc79eb327e..f573d9e3715e2 100755 --- a/testsuite/single_node_performance.py +++ b/testsuite/single_node_performance.py @@ -288,6 +288,9 @@ class RunGroupConfig: RunGroupConfig(key=RunGroupKey("no-op-fee-payer", module_working_set_size=DEFAULT_MODULE_WORKING_SET_SIZE), included_in=Flow.CONTINUOUS), RunGroupConfig(key=RunGroupKey("simple-script"), included_in=LAND_BLOCKING_AND_C, waived=True), + RunGroupConfig(expected_tps=394, key=RunGroupKey("vector-trim-append-len3000-size1"), included_in=Flow.CONTINUOUS, waived=True), + RunGroupConfig(expected_tps=398, key=RunGroupKey("vector-remove-insert-len3000-size1"), included_in=Flow.CONTINUOUS, waived=True), + RunGroupConfig(expected_tps=50000, key=RunGroupKey("coin_transfer_connected_components", executor_type="sharded"), key_extra=RunGroupKeyExtra(sharding_traffic_flags="--connected-tx-grps 5000", transaction_type_override=""), included_in=Flow.REPRESENTATIVE, waived=True), RunGroupConfig(expected_tps=50000, key=RunGroupKey("coin_transfer_hotspot", executor_type="sharded"), key_extra=RunGroupKeyExtra(sharding_traffic_flags="--hotspot-probability 0.8", transaction_type_override=""), included_in=Flow.REPRESENTATIVE, waived=True),