Skip to content

Commit 27c473a

Browse files
committed
addressing review comments
1 parent e7dd9ff commit 27c473a

File tree

1 file changed

+42
-41
lines changed
  • apps/nextra/pages/en/build/smart-contracts

1 file changed

+42
-41
lines changed

apps/nextra/pages/en/build/smart-contracts/maps.mdx

+42-41
Original file line numberDiff line numberDiff line change
@@ -9,73 +9,72 @@ We will go over their differences and similarities, and how to choose which one
99

1010
## Aptos Blockchain performance and gas cost considerations
1111

12-
State on the Aptos Blockchain is managed as a set of resources. Transactions
13-
performance heavily depends on how reads and writes to resources.
14-
Storage gas costs are paid based on number of resources that exist, and their sizes.
15-
IO gas costs are paid based on number of resources read and modified, and their sizes,
16-
but are generally significantly smaller than storage gas costs.
17-
That means that writing to a new resource has the highest (storage) gas cost, and deleting
18-
an existing resource gives the largest refund.
19-
Additionally, transactions modifying the same resource conflict with one another, and cannot be
20-
executed in parallel.
12+
Aptos Blockchain state is managed through on-chain **resources**.
13+
Furthermore, transaction performance and gas cost is heavily influenced by how these resources are read and written.
14+
Breaking down the gas costs further, we have:
15+
1. Storage fee, which are determined by the number and size of resources (i.e., writing to a new resource incurs the highest storage fee, whereas deleting an existing resource provides the largest refund.)
16+
2. IO gas costs —generally much lower— which depend on the number and size of resources read and modified.
17+
3. execution gas costs are based on the computation needed, and are generally in the similar scale as io gas costs.
18+
19+
Transactions that modify the same resource cannot be executed in parallel, as they conflict with one another.
2120

2221
One useful analogy is thinking about each resource being a file on a disk,
2322
then performance of smart contract would correlate well to a program that
2423
operates on files in the same way.
2524

2625
## Different Map implementations
2726

28-
- `OrderedMap` is a struct, and is, similar to `vector`, fully contained within the resource that stores it.
29-
With it, it is bounded in size to the size of a single resource.
30-
It provides regular map functions, as well as accessing elements in order, like front/back or prev/next.
31-
When you need an inline mapping, that will fit in a resource, this is the option to choose.
32-
It's implementation is SortedVectorMap, but because of limited size and efficiency of memcpy, all main operations are practically O(log(n)).
33-
- `Table` is unbounded in size, puts each (key, value) pair in the separate resource. You can `add` or `remove` elements,
34-
or check if it `contains` some key, but cannot be iterated on. When keys or values are large / unbounded, we can use the `Table`.
35-
Also if we want to parallelize transactions and we have a few elements that are modified extremely often, `Table` can provide that.
36-
Note that `Table` cannot be destroyed, because it doesn't know if it is empty.
37-
- `TableWithLength` is wrapper around the `Table`, that adds tracking of it's `length`, allowing `length`, `empty` and `destroy_empty`
38-
operations on top of the `Table`. Adding or removing elements to `TableWithLength` cannot be done in parallel.
39-
- `BigOrderedMap` groups multiple (key, value) pairs in a single resource, but is unbounded in size - and uses more resources as needed.
40-
It's implementation is a BPlusTreeMap, where each node is a resource containing OrderedMap, with inner nodes only containing keys, while leaves contain values as well.
41-
It is opportunistically parallel - if map has large enough elements to be using multiple resources, modifying the map for keys that are not close
42-
to each other should generally be parallel operation.
43-
It is configured so that each resource containing internal node has the same capacity in number of keys,
44-
and each resource containing leaf node has the same capacity in the number of (key, value) pairs.
45-
Capacity of nodes (both leaf and inner degree) are configurable - to allow the tradeoff between storage gas cost on one end,
46-
and other gas costs and parallelism on the other.
47-
It provides regular map functions, as well as accessing elements in order, like front/back or prev/next.
27+
| Implementation | Size Limit | Storage Structure | Key Features |
28+
|--------------------|------------|------------------|--------------|
29+
| `OrderedMap` | Bounded (fits in a single resource) | Stored entirely within the resource | Supports ordered access (front/back, prev/next), implemented as sorted vector, but operations are effectively O(log(n)) due to internal optimizations |
30+
| `Table` | Unbounded | Each (key, value) stored in a separate resource | Supports basic operations, like `add`, `remove`, `contains`, but **not iteration**, and **cannot be destroyed**; useful for large/unbounded keys/values and high-parallelism cases |
31+
| `TableWithLength` | Unbounded | same as `Table` | Variant of `Table`, with additional length tracking, which adds `length`, `empty`, and `destroy_empty` methods; Adding or removing elements **cannot** be done in parallel, modifying existing elements can. |
32+
| `BigOrderedMap` | Unbounded | Combines multiple keys into a single resource, and grows into multiple resources dynamically | Implemented as B+ tree; **opportunistically parallel** for non-adjacent keys; supports ordered access (front/back, prev/next); configurable node capacities to balance storage and performance |
4833

4934
Note:
5035
- `SimpleMap` has been deprecated, and replaced with `OrderedMap`.
5136
- `SmartTable` has been deprecated, and replaced with `BigOrderedMap`.
5237

38+
#### Performance comparison
39+
40+
We measured performance at small scale, measuring microseconds taken for a single pair of `insert` + `remove` operation, into a map of varied size.
41+
42+
| num elements | OrderedMap | BigOrderedMap all inlined | BigOrderedMap max_degree=16 |
43+
|--------------|------------|---------------------------|-----------------------------|
44+
| 10 | 65 | 123 | 123 |
45+
| 100 | 85 | 146 | 455 |
46+
| 1000 | 105 | 168 | 567 |
47+
| 10000 | 142 | 210 | 656 |
48+
49+
You can see that overhead of `BigOrderedMap` compared to `OrderedMap`, when both are in the single resource, is around 1.5-2x.
50+
So you can generally used `BigOrdredMap` when it is unknown if data will be too large to be stored in a single resource.x
51+
5352
## Common map operations:
5453

5554
Most maps above support the same set of functions (for actual signatures and restrictions, check out the corresponding implementations):
5655

57-
#### Creating Tables
56+
#### Creating Maps
5857

5958
- `new<K, V>(): Self`: creates an empty map
6059

61-
#### Destroying Tables
60+
#### Destroying Maps
6261

6362
All except `Table` support:
64-
- `destroy_empty<K, V>(table: Self<K, V>)`: Destroys an empty map. (not supported by `Table`)
65-
- `destroy<K, V>(self: Self<K, V>, dk: |K|, dv: |V|)`: Destroys a map with given functions that destroy correponding elements. (not supported by `Table` and `TableWithLength`)
63+
- `destroy_empty<K, V>(self: Self<K, V>)`: Destroys an empty map. (**not** supported by `Table`)
64+
- `destroy<K, V>(self: Self<K, V>, dk: |K|, dv: |V|)`: Destroys a map with given functions that destroy correponding elements. (**not** supported by `Table` and `TableWithLength`)
6665

6766
#### Managing Entries
6867

69-
- `add<K, V>(table: &mut Self<K, V>, key: K, value: V)`: Adds a key-value pair to the map.
70-
- `remove<K, V>(table: &mut Self<K, V>, key: K): V`: Removes and returns the value associated with a key.
71-
- `upsert<K, V>(table: &mut Self<K, V>, key: K, value: V): Option<V>`: Inserts or updates a key-value pair.
72-
- `add_all<K, V>(table: &mut Self<K, V>, keys: vector<K>, values: vector<V>)`: Adds multiple key-value pairs to the map. (not supported by `Table` and `TableWithLength`)
68+
- `add<K, V>(self: &mut Self<K, V>, key: K, value: V)`: Adds a key-value pair to the map.
69+
- `remove<K, V>(self: &mut Self<K, V>, key: K): V`: Removes and returns the value associated with a key.
70+
- `upsert<K, V>(self: &mut Self<K, V>, key: K, value: V): Option<V>`: Inserts or updates a key-value pair.
71+
- `add_all<K, V>(self: &mut Self<K, V>, keys: vector<K>, values: vector<V>)`: Adds multiple key-value pairs to the map. (**not** supported by `Table` and `TableWithLength`)
7372

7473
#### Retrieving Entries
7574

7675
- `contains<K, V>(self: &Self<K, V>, key: &K): bool`: Checks whether key exists in the map.
77-
- `borrow<K, V>(table: &Self<K, V>, key: &K): &V`: Returns an immutable reference to the value associated with a key.
78-
- `borrow_mut<K: drop, V>(table: &mut Self<K, V>, key: K): &mut V`: Returns a mutable reference to the value associated with a key.
76+
- `borrow<K, V>(self: &Self<K, V>, key: &K): &V`: Returns an immutable reference to the value associated with a key.
77+
- `borrow_mut<K: drop, V>(self: &mut Self<K, V>, key: K): &mut V`: Returns a mutable reference to the value associated with a key.
7978
(`BigOrderedMap` only allows `borrow_mut` when value type has a static constant size, due to modification being able to break it's invariants otherwise. Use `remove()` and `add()` combination instead)
8079

8180
#### Order-dependant functions
@@ -91,7 +90,7 @@ These set of functions are only implemented by `OrderedMap` and `BigOrderedMap`.
9190

9291
#### Utility Functions
9392

94-
- `length<K, V>(table: &Self<K, V>): u64`: Returns the number of entries in the table. (not supported by `Table`)
93+
- `length<K, V>(self: &Self<K, V>): u64`: Returns the number of entries in the map. (not supported by `Table`)
9594

9695
#### Traversal Functions
9796

@@ -134,7 +133,7 @@ module 0x42::map_usage {
134133
}
135134
```
136135

137-
## Additional details for BigOrderedMap
136+
## Additional details for `BigOrderedMap`
138137

139138
Its current implementation is B+ tree, which is chosen as it is best suited for the onchain storage layout - where the majority of cost comes from loading and writing to storage items, and there is no partial read/write of them.
140139

@@ -146,6 +145,8 @@ Implementation has few characteristics that make it very versatile and useful ac
146145
- One caveat, is refundable storage fee. By default, operation that requires map to grow to more resources needs to pay for storage fee for it. Implementation here has an option to pre-pay for storage slots, and to reuse them as elements are added/removed, allowing applications to achieve fully predictable overall gas charges, if needed.
147146
- If key/value is within the size limits map was configured with, inserts will never fail unpredictably, as map internally understands and manages maximal resource size limits.
148147

148+
#### Creating `BigOrderedMap`
149+
149150
Because it's layout affects what can be inserted and performance, there are a few ways to create and configure it:
150151

151152
- `new<K, V>(): Self<K, V>`: Returns a new `BigOrderedMap` with the default configuration. Only allowed to be called with constant size types. For variable sized types, another constructor is needed, to explicitly select automatic or specific degree selection.

0 commit comments

Comments
 (0)