Skip to content

Commit

Permalink
[move-stdlib] Use vector::move_range inside vector, and evaluate perf…
Browse files Browse the repository at this point in the history
…ormance / calibrate gas (#14862)

## Description
Use vector::move_range inside of vector, to optimize `insert`, `remove`, `append`, `trim`. 
Extend aptos-move/e2e-benchmark/src/main.rs to track gas and gas/s, to allow for quick calibration. 
Adding workloads to txn-emitter to be able to use it throughput.

Additionally add a missing `replace` method, which replaces value at particular index. 

Running on extended set of params:
https://gist.github.com/igor-aptos/e8d4e21edcbc75dddcb9382d4e077665

Summary of the performance tests:
- performance doesn't depend on the size of the values (unless they are primitive), as vector modifies only pointers to them.
- operation depends very little on how many elements we need to move - moving 1000 elements (i.e. to insert into a vector 1000 elements from the end) is only (!!) 2x slower than moving 1 element, end-to-end. 

For gas calibration, on the variety of workloads, current implementation has decent variance. After tuning params to match the averages, variance seems much smaller.
  • Loading branch information
igor-aptos authored Dec 6, 2024
1 parent 95e0d68 commit 5902ff0
Show file tree
Hide file tree
Showing 15 changed files with 870 additions and 36 deletions.
9 changes: 9 additions & 0 deletions aptos-move/e2e-benchmark/data/calibration_values.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,12 @@ FungibleAssetMint 60 0.930 1.098 235.8
IncGlobalMilestoneAggV2 { milestone_every: 1 } 60 0.914 1.051 33.5
IncGlobalMilestoneAggV2 { milestone_every: 2 } 60 0.914 1.105 19.0
EmitEvents { count: 1000 } 60 0.937 1.158 8818.7
VectorTrimAppend { vec_len: 3000, element_len: 1, index: 0, repeats: 0 } 6 0.925 1.001 6058.1
VectorTrimAppend { vec_len: 3000, element_len: 1, index: 100, repeats: 1000 } 6 0.925 1.001 34000.0
VectorTrimAppend { vec_len: 3000, element_len: 1, index: 2990, repeats: 1000 } 6 0.925 1.001 17626.5
VectorRemoveInsert { vec_len: 3000, element_len: 1, index: 100, repeats: 1000 } 6 0.925 1.001 30870.3
VectorRemoveInsert { vec_len: 3000, element_len: 1, index: 2998, repeats: 1000 } 6 0.925 1.001 20343.2
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 277.0
VectorTrimAppend { vec_len: 100, element_len: 100, index: 10, repeats: 1000 } 6 0.925 1.001 12146.6
VectorRangeMove { vec_len: 100, element_len: 100, index: 50, move_len: 10, repeats: 1000 } 6 0.925 1.001 7098
60 changes: 60 additions & 0 deletions aptos-move/e2e-benchmark/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,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();
Expand Down
57 changes: 57 additions & 0 deletions aptos-move/framework/move-stdlib/doc/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -671,6 +673,15 @@ Lifetime: transient



<a id="0x1_features_NATIVE_MEMORY_OPERATIONS"></a>



<pre><code><b>const</b> <a href="features.md#0x1_features_NATIVE_MEMORY_OPERATIONS">NATIVE_MEMORY_OPERATIONS</a>: u64 = 80;
</code></pre>



<a id="0x1_features_NEW_ACCOUNTS_DEFAULT_TO_FA_APT_STORE"></a>

Lifetime: transient
Expand Down Expand Up @@ -3274,6 +3285,52 @@ Deprecated feature



</details>

<a id="0x1_features_get_native_memory_operations_feature"></a>

## Function `get_native_memory_operations_feature`



<pre><code><b>public</b> <b>fun</b> <a href="features.md#0x1_features_get_native_memory_operations_feature">get_native_memory_operations_feature</a>(): u64
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> <b>fun</b> <a href="features.md#0x1_features_get_native_memory_operations_feature">get_native_memory_operations_feature</a>(): u64 { <a href="features.md#0x1_features_NATIVE_MEMORY_OPERATIONS">NATIVE_MEMORY_OPERATIONS</a> }
</code></pre>



</details>

<a id="0x1_features_is_native_memory_operations_enabled"></a>

## Function `is_native_memory_operations_enabled`



<pre><code><b>public</b> <b>fun</b> <a href="features.md#0x1_features_is_native_memory_operations_enabled">is_native_memory_operations_enabled</a>(): bool
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> <b>fun</b> <a href="features.md#0x1_features_is_native_memory_operations_enabled">is_native_memory_operations_enabled</a>(): bool <b>acquires</b> <a href="features.md#0x1_features_Features">Features</a> {
<a href="features.md#0x1_features_is_enabled">is_enabled</a>(<a href="features.md#0x1_features_NATIVE_MEMORY_OPERATIONS">NATIVE_MEMORY_OPERATIONS</a>)
}
</code></pre>



</details>

<a id="0x1_features_change_feature_flags"></a>
Expand Down
131 changes: 118 additions & 13 deletions aptos-move/framework/move-stdlib/doc/vector.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -146,6 +147,18 @@ The length of the vectors are not equal.



<a id="0x1_vector_USE_MOVE_RANGE"></a>

Whether to utilize native vector::move_range
Vector module cannot call features module, due to cyclic dependency,
so this is a constant.


<pre><code><b>const</b> <a href="vector.md#0x1_vector_USE_MOVE_RANGE">USE_MOVE_RANGE</a>: bool = <b>true</b>;
</code></pre>



<a id="0x1_vector_empty"></a>

## Function `empty`
Expand Down Expand Up @@ -482,8 +495,15 @@ Pushes all of the elements of the <code>other</code> vector into the <code>self<


<pre><code><b>public</b> <b>fun</b> <a href="vector.md#0x1_vector_append">append</a>&lt;Element&gt;(self: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;Element&gt;, other: <a href="vector.md#0x1_vector">vector</a>&lt;Element&gt;) {
<a href="vector.md#0x1_vector_reverse">reverse</a>(&<b>mut</b> other);
<a href="vector.md#0x1_vector_reverse_append">reverse_append</a>(self, other);
<b>if</b> (<a href="vector.md#0x1_vector_USE_MOVE_RANGE">USE_MOVE_RANGE</a>) {
<b>let</b> self_length = <a href="vector.md#0x1_vector_length">length</a>(self);
<b>let</b> other_length = <a href="vector.md#0x1_vector_length">length</a>(&other);
<a href="vector.md#0x1_vector_move_range">move_range</a>(&<b>mut</b> other, 0, other_length, self, self_length);
<a href="vector.md#0x1_vector_destroy_empty">destroy_empty</a>(other);
} <b>else</b> {
<a href="vector.md#0x1_vector_reverse">reverse</a>(&<b>mut</b> other);
<a href="vector.md#0x1_vector_reverse_append">reverse_append</a>(self, other);
}
}
</code></pre>

Expand Down Expand Up @@ -525,7 +545,11 @@ Pushes all of the elements of the <code>other</code> vector into the <code>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 <code>split_off</code>.


<pre><code><b>public</b> <b>fun</b> <a href="vector.md#0x1_vector_trim">trim</a>&lt;Element&gt;(self: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;Element&gt;, new_len: u64): <a href="vector.md#0x1_vector">vector</a>&lt;Element&gt;
Expand All @@ -538,9 +562,21 @@ Trim a vector to a smaller size, returning the evicted elements in order


<pre><code><b>public</b> <b>fun</b> <a href="vector.md#0x1_vector_trim">trim</a>&lt;Element&gt;(self: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;Element&gt;, new_len: u64): <a href="vector.md#0x1_vector">vector</a>&lt;Element&gt; {
<b>let</b> res = <a href="vector.md#0x1_vector_trim_reverse">trim_reverse</a>(self, new_len);
<a href="vector.md#0x1_vector_reverse">reverse</a>(&<b>mut</b> res);
res
<b>let</b> len = <a href="vector.md#0x1_vector_length">length</a>(self);
<b>assert</b>!(new_len &lt;= len, <a href="vector.md#0x1_vector_EINDEX_OUT_OF_BOUNDS">EINDEX_OUT_OF_BOUNDS</a>);

<b>let</b> other = <a href="vector.md#0x1_vector_empty">empty</a>();
<b>if</b> (<a href="vector.md#0x1_vector_USE_MOVE_RANGE">USE_MOVE_RANGE</a>) {
<a href="vector.md#0x1_vector_move_range">move_range</a>(self, new_len, len - new_len, &<b>mut</b> other, 0);
} <b>else</b> {
<b>while</b> (len &gt; new_len) {
<a href="vector.md#0x1_vector_push_back">push_back</a>(&<b>mut</b> other, <a href="vector.md#0x1_vector_pop_back">pop_back</a>(self));
len = len - 1;
};
<a href="vector.md#0x1_vector_reverse">reverse</a>(&<b>mut</b> other);
};

other
}
</code></pre>

Expand Down Expand Up @@ -728,10 +764,27 @@ Aborts if out of bounds.
<pre><code><b>public</b> <b>fun</b> <a href="vector.md#0x1_vector_insert">insert</a>&lt;Element&gt;(self: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;Element&gt;, i: u64, e: Element) {
<b>let</b> len = <a href="vector.md#0x1_vector_length">length</a>(self);
<b>assert</b>!(i &lt;= len, <a href="vector.md#0x1_vector_EINDEX_OUT_OF_BOUNDS">EINDEX_OUT_OF_BOUNDS</a>);
<a href="vector.md#0x1_vector_push_back">push_back</a>(self, e);
<b>while</b> (i &lt; len) {
<a href="vector.md#0x1_vector_swap">swap</a>(self, i, len);
i = i + 1;

<b>if</b> (<a href="vector.md#0x1_vector_USE_MOVE_RANGE">USE_MOVE_RANGE</a>) {
<b>if</b> (i + 2 &gt;= len) {
// When we are close <b>to</b> the end, it is cheaper <b>to</b> not create
// a temporary <a href="vector.md#0x1_vector">vector</a>, and swap directly
<a href="vector.md#0x1_vector_push_back">push_back</a>(self, e);
<b>while</b> (i &lt; len) {
<a href="vector.md#0x1_vector_swap">swap</a>(self, i, len);
i = i + 1;
};
} <b>else</b> {
<b>let</b> other = <a href="vector.md#0x1_vector_singleton">singleton</a>(e);
<a href="vector.md#0x1_vector_move_range">move_range</a>(&<b>mut</b> other, 0, 1, self, i);
<a href="vector.md#0x1_vector_destroy_empty">destroy_empty</a>(other);
}
} <b>else</b> {
<a href="vector.md#0x1_vector_push_back">push_back</a>(self, e);
<b>while</b> (i &lt; len) {
<a href="vector.md#0x1_vector_swap">swap</a>(self, i, len);
i = i + 1;
};
};
}
</code></pre>
Expand Down Expand Up @@ -763,9 +816,25 @@ Aborts if <code>i</code> is out of bounds.
// i out of bounds; <b>abort</b>
<b>if</b> (i &gt;= len) <b>abort</b> <a href="vector.md#0x1_vector_EINDEX_OUT_OF_BOUNDS">EINDEX_OUT_OF_BOUNDS</a>;

len = len - 1;
<b>while</b> (i &lt; len) <a href="vector.md#0x1_vector_swap">swap</a>(self, i, { i = i + 1; i });
<a href="vector.md#0x1_vector_pop_back">pop_back</a>(self)
<b>if</b> (<a href="vector.md#0x1_vector_USE_MOVE_RANGE">USE_MOVE_RANGE</a>) {
// When we are close <b>to</b> the end, it is cheaper <b>to</b> not create
// a temporary <a href="vector.md#0x1_vector">vector</a>, and swap directly
<b>if</b> (i + 3 &gt;= len) {
len = len - 1;
<b>while</b> (i &lt; len) <a href="vector.md#0x1_vector_swap">swap</a>(self, i, { i = i + 1; i });
<a href="vector.md#0x1_vector_pop_back">pop_back</a>(self)
} <b>else</b> {
<b>let</b> other = <a href="vector.md#0x1_vector_empty">empty</a>();
<a href="vector.md#0x1_vector_move_range">move_range</a>(self, i, 1, &<b>mut</b> other, 0);
<b>let</b> result = <a href="vector.md#0x1_vector_pop_back">pop_back</a>(&<b>mut</b> other);
<a href="vector.md#0x1_vector_destroy_empty">destroy_empty</a>(other);
result
}
} <b>else</b> {
len = len - 1;
<b>while</b> (i &lt; len) <a href="vector.md#0x1_vector_swap">swap</a>(self, i, { i = i + 1; i });
<a href="vector.md#0x1_vector_pop_back">pop_back</a>(self)
}
}
</code></pre>

Expand Down Expand Up @@ -838,6 +907,42 @@ Aborts if <code>i</code> is out of bounds.



</details>

<a id="0x1_vector_replace"></a>

## Function `replace`

Replace the <code>i</code>th element of the vector <code>self</code> with the given value, and return
to the caller the value that was there before.
Aborts if <code>i</code> is out of bounds.


<pre><code><b>public</b> <b>fun</b> <a href="vector.md#0x1_vector_replace">replace</a>&lt;Element&gt;(self: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;Element&gt;, i: u64, val: Element): Element
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> <b>fun</b> <a href="vector.md#0x1_vector_replace">replace</a>&lt;Element&gt;(self: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;Element&gt;, i: u64, val: Element): Element {
<b>let</b> last_idx = <a href="vector.md#0x1_vector_length">length</a>(self);
<b>assert</b>!(i &lt; last_idx, <a href="vector.md#0x1_vector_EINDEX_OUT_OF_BOUNDS">EINDEX_OUT_OF_BOUNDS</a>);
// TODO: Enable after tests are fixed.
// <b>if</b> (<a href="vector.md#0x1_vector_USE_MOVE_RANGE">USE_MOVE_RANGE</a>) {
// <a href="mem.md#0x1_mem_replace">mem::replace</a>(<a href="vector.md#0x1_vector_borrow_mut">borrow_mut</a>(self, i), val)
// } <b>else</b> {
<a href="vector.md#0x1_vector_push_back">push_back</a>(self, val);
<a href="vector.md#0x1_vector_swap">swap</a>(self, i, last_idx);
<a href="vector.md#0x1_vector_pop_back">pop_back</a>(self)
// }
}
</code></pre>



</details>

<a id="0x1_vector_for_each"></a>
Expand Down
Loading

0 comments on commit 5902ff0

Please sign in to comment.