Aptos 链上的交易,在不思考市场供需的状况下,会收取一笔“根底 Gas 费”。它是由以下 3 局部组成:
- 指令
- 存储
- 载荷
一笔交易含有越多的函数调用,分支判断之类的简单逻辑,就耗费越多的“指令”Gas。相应地,如果交易中有越多的读写申请,就耗费越多的“存储”Gas。而一笔交易上附带的载荷(payload)越长(字节数多),就耗费越多的“载荷”Gas。
如 优化准则 一节所形容的,在根底 Gas 费中,存储 Gas 占用的比重最大。
指令 Gas
根底指令 Gas 参数在 instr.rs 中有残缺定义,这里列出他们的次要信息:
无操作
参数 | 含意 |
---|---|
nop | 无操作 |
流程管制
参数 | 含意 |
---|---|
ret | 返回 |
abort | 退出 |
br_true | 执行条件为 true 的分支 |
br_false | 执行条件为 false 的分支 |
branch | 分支 |
栈(Stack)
参数 | 含意 |
---|---|
pop | 弹出 |
ld_u8 | Load a u8 |
ld_u64 | Load a u64 |
ld_u128 | Load a u128 |
ld_true | Load a true |
ld_false | Load a false |
ld_const_base | 加载 constant 的根底生产 |
ld_const_per_byte | 加载 constant 的每字节生产 |
本地作用域
参数 | 含意 |
---|---|
imm_borrow_loc | 不可变更的 borrow(权限) |
mut_borrow_loc | 可变更的 borrow |
imm_borrow_field | 不可变更的字段 borrow(权限) |
mut_borrow_field | 可变更的字段 borrow |
imm_borrow_field_generic | |
mut_borrow_field_generic | |
copy_loc_base | copy 的根底生产 |
copy_loc_per_abs_val_unit | |
move_loc_base | Move |
st_loc_base |
调用
参数 | 含意 |
---|---|
call_base | 函数调用的根底生产 |
call_per_arg | 函数调用的每参数生产 |
call_generic_base | |
call_generic_per_ty_arg | 每个类型参数生产 |
call_generic_per_arg |
构造体(Structs)
参数 | 含意 |
---|---|
pack_base | 打包构造体的根底生产 |
pack_per_field | 打包构造体的每字段生产 |
pack_generic_base | |
pack_generic_per_field | |
unpack_base | 解包构造体的根底生产 |
unpack_per_field | 解包构造体的每字段生产 |
unpack_generic_base | |
unpack_generic_per_field |
援用(References)
参数 | 含意 |
---|---|
read_ref_base | 读取援用的根底生产 |
read_ref_per_abs_val_unit | |
write_ref_base | 写入援用的根底生产 |
freeze_ref | 解冻援用 |
类型转换(Casting)
参数 | 含意 |
---|---|
cast_u8 | 转为 u8 |
cast_u64 | 转为 u64 |
cast_u128 | 转为 u128 |
代数运算(Arithmetic)
参数 | 含意 |
---|---|
add | 加 |
sub | 减 |
mul | 乘 |
mod_ | 模(取余) |
div | 除 |
位运算(Bitwise)
参数 | 含意 |
---|---|
bit_or | 按位或: | |
bit_and | 按位与: & |
xor | 异或: ^ |
shl | 左移: << |
shr | 右移: >> |
逻辑运算(Boolean)
参数 | 含意 |
---|---|
or | 或: || |
and | 与: && |
not | 非: ! |
比拟运算(Comparison)
参数 | 含意 |
---|---|
lt | 小于: < |
gt | 大于: > |
le | 小于等于: <= |
ge | 大于等于: >= |
eq_base | 根底相等: == |
eq_per_abs_val_unit | (绝对值相等?) |
neq_base | 根底不等: != |
neq_per_abs_val_unit |
全局存储(Global storage)
参数 | 含意 |
---|---|
imm_borrow_global_base | 不可变更 borrow 的根底生产: borrow_global<T>() |
imm_borrow_global_generic_base | |
mut_borrow_global_base | 可变更 borrow 的根底生产: borrow_global_mut<T>() |
mut_borrow_global_generic_base | |
exists_base | 查看是否存在的根底生产: exists<T>() |
exists_generic_base | |
move_from_base | move from 根底生产: move_from<T>() |
move_from_generic_base | |
move_to_base | move to 根底生产: move_to<T>() |
move_to_generic_base |
向量运算(Vectors)
参数 | 含意 |
---|---|
vec_len_base | 向量长度 |
vec_imm_borrow_base | 不可变更地 borrow 一个元素 |
vec_mut_borrow_base | 可变更地 borrow 一个元素 |
vec_push_back_base | 压回 |
vec_pop_back_base | 弹出 |
vec_swap_base | 替换元素 |
vec_pack_base | 打包向量的根底生产 |
vec_pack_per_elem | 打包向量的每元素生产 |
vec_unpack_base | 解包向量的根底生产 |
vec_unpack_per_expected_elem | 解包向量的每元素生产 |
更多存储想改的 gas 参数,详见 table.rs, move_stdlib.rs,其余相干源文件在这里 aptos-gas/src/.
存储 gas
存储 Gas 参数定义在 storage_gas.move 中,外面还蕴含了一个残缺的 DocGen 文件:storage_gas.md.
简略说:
- 在初始化函数中(initialize()),有一个 base_8192_exponential_curve() 办法,用于生成一条指数曲线,当存储利用率靠近下限时,每元素和每字节的老本将疾速回升。
- 在每个世代中,都能够通过 on_reconfig() 来配置基于元素或字节利用率的参数。
-
这些参数存储在 StorageGas 中,共蕴含下列字段:
字段 含意 per_item_read 从全局存储读取一个元素的生产 per_item_create 在全局存储创立一个元素的生产 per_item_write 向全局存储写入一个元素的生产 per_byte_read 从全局存储读取一个字节的生产 per_byte_create 在全局存储创立一个字节的生产 per_byte_write 向全局存储写入一个字节的生产
这里,所谓一个元素,或者是一个带 key 属性的资源,或者 table 中的一条入口记录;而 per-byte 类型的消费量,是依据元素的整体大小来评估的。例如在 storage_gas.md 的形容里,如果一个操作批改了某项资源中的 u8 类型字段,而该资源还有 5 个 u128 类型的资源,那么 per-byte gas 的计算公式为:(5 * 128) / 8 + 1 = 81 bytes
.
向量
向量的按字节付费计算是相似的:
其中:
- nn 是向量中元素的格局
- ei 是元素 i 的容量
- b(n) 是函数 n 的根底容量
对于向量根底容量 (技术上是 ULEB128) 的更多信息,能够查看文档 BCS sequence specification,它在实践中通常只占一个字节,所以,含 100 个 u8 类型元素的向量,占用 100 + 1 = 101 个字节。因而,根据上述的逐项读取办法,读取这样一个向量的最初一个元素被视为一个 101 字节的读取。
载荷 gas
载荷 gas 的参数,定义在文档 transaction.rs 中,它们把带有效载荷的存储 gas 与价格关联起来:
参数 | 含意 |
---|---|
min_transaction_gas_units | 一笔交易的最小 gas 数,在交易开始执行的时候收取 |
large_transaction_cutoff | 以字节为单位的大小限度,交易大小超过这个值的话,会按字节收取费用 |
intrinsic_gas_per_byte | 对于超出 large_transaction_cutoff 约定大小的交易载荷,每字节收取的 gas 数 |
maximum_number_of_gas_units | 交易中内部 gas 数下限 |
min_price_per_gas_unit | 交易最小 gas 价格 |
max_price_per_gas_unit | 交易最大 gas 价格 |
max_transaction_size_in_bytes | 交易载荷最大字节数 |
gas_unit_scaling_factor | 外部 gas 和内部 gas 数量转换系数 |
在这里,“外部 gas 数”是定义在源文件 instr.rs 和 storage_gas.move 中的常量,它比“内部 gas 数”更细粒度一些,要想把“外部 gas 数”转换为“内部 gas 数”须要除以系数 gas_unit_scaling_factor。要把“内部 gas 数”转为 octas 为单位的货币,再乘以 “gas price”,也就是每单位内部 gas 的价格。
优化准则
单位与价格常量
在本文档编写的时候,每 gas 单位最小价格(min_price_per_gas_unit)是定义在文件 transaction.rs 中的一个全局常量:aptos_global_constants::GAS_UNIT_PRICE(目前等于 100)。该文件还定义了其余重要的常量:
常量 | 值 |
---|---|
min_price_per_gas_unit | 100 |
max_price_per_gas_unit | 10,000 |
gas_unit_scaling_factor | 10,000 |
Payload gas 一节具体解释了这些常量的含意
存储 gas
在本文编写的时候,初始化(initialize())办法设置了下列最低存储 gas 数量:
数据类型 | 操作 | 符号 | 最小外部 gas 数 |
---|---|---|---|
Per item | Read | r_iri | 300,000 |
Per item | Create | c_ici | 5,000,000 |
Per item | Write | w_iwi | 300,000 |
Per byte | Read | r_brb | 300 |
Per byte | Create | c_bcb | 5,000 |
Per byte | Write | w_bwb | 5,000 |
最大数量是最小数量的 100 倍,这意味着,当利用率不到 40%,总的 gas 生产会在最小数量的 1 到 1.5 倍区间内(算法详见 base_8192_exponential_curve()。因而,以 octas 计价的话,主网络的初始 gas 生产如下表所示:(除以外部 gas 比例因子,再乘以最小 gas 价格):
操作 | 符号 | 最小 octas 破费 |
---|---|---|
Per-item read | r_iri | 3000 |
Per-item create | c_ici | 50,000 |
Per-item write | w_iwi | 3000 |
Per-byte read | r_brb | 3 |
Per-byte create | c_bcb | 50 |
Per-byte write | w_bwb | 50 |
咱们能够看到,最低廉的 per-item 模式,是创立一个新元素(通过 move_to<T>() 办法,或者减少一行元素到 table),简直比读写一个已有元素贵了 17 倍:
此外,
- per-item 模式中,读和写的破费是相等的:
- per-byte 模式中,写操作却跟创立操作一样贵:
- per-byte 模式中,写操作和创立操作的破费,简直是读操作的 17 倍:
- Per-item 读的破费是 per-byte 读的 1000 倍:
- Per-item 创立的破费是 per-byte 创立的 1000 倍:
- Per-item 写的破费是 per-byte 写的 60 倍:%22%20aria-hidden%3D%22true%22%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMATHI-63%22%20x%3D%220%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%20%3Cuse%20transform%3D%22scale(0.707)%22%20xlink%3Ahref%3D%22%23E1-MJMATHI-69%22%20x%3D%22613%22%20y%3D%22-213%22%3E%3C%2Fuse%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMAIN-3D%22%20x%3D%221055%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%3Cg%20transform%3D%22translate(2111%2C0)%22%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMAIN-31%22%3E%3C%2Fuse%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMAIN-30%22%20x%3D%22500%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMAIN-30%22%20x%3D%221001%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMAIN-30%22%20x%3D%221501%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%3C%2Fg%3E%0A%3Cg%20transform%3D%22translate(4113%2C0)%22%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMATHI-63%22%20x%3D%220%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%20%3Cuse%20transform%3D%22scale(0.707)%22%20xlink%3Ahref%3D%22%23E1-MJMATHI-62%22%20x%3D%22613%22%20y%3D%22-213%22%3E%3C%2Fuse%3E%0A%3C%2Fg%3E%0A%3C%2Fg%3E%0A%3C%2Fsvg%3E#card=math&code=w_i%20%3D%2060%20w_b&id=zuuM5)
读和创立的 per-item 比 per-byte 是 1000 倍,而且写操作的 per-item 比 per-byte,只有 60 倍。
这样,因为不足对节俭全局存储(比方 删除元素 move_from<T>(),或者删除行 removing from a table)的激励措施,目前无效的存储 gas 优化策略如下:
- 尽量避免 per-item 模式的创立操作
- 放弃对未应用元素的追踪,尽量覆写它们,而不是创立新的元素
- 尽量避免 per-item 模式的写操作
- 尽量多读少写
-
尽量减少所以操作中的字节数,特地是写操作
指令 gas
在本文编写的时候,所有的指令 gas 都乘以一个在 gas_meter.rs 中定义的常量 EXECUTION_GAS_MULTIPLIER,目前是 20。因而,以下有代表性的操作假如 Gas 老本如下:(外部 gas 除以比例系数,而后乘以最低 gas 价格):
操作 | 最小 octas 破费 |
---|---|
Table add/borrow/remove box | 240 |
Function call | 200 |
Load constant | 130 |
Globally borrow | 100 |
Read/write reference | 40 |
Load u128 on stack | 16 |
Table box operation per byte | 2 |
(留神 per-byte 模式的 table box 操作指令 gas,并不蕴含相应的存储 gas,它们是各自独立核算的)
作为比拟,读取一个 100 字节的元素须要破费 个 octas,大概是函数调用的 16.5 倍,一般来说,指令 gas 老本次要由存储 gas 老本主导。
因而,值得注意的是,只管从技术上讲,缩小程序中的函数调用次数是有价值的,但工程实际中,咱们简直在所有优化中,都是致力于编写模块化、可分解的代码,并以缩小存储 gas 老本为指标,而不是试图编写具备较少嵌套函数的反复代码块。
在极其状况下,指令 gas 当然是有可能大大超过存储 gas 老本的,比方实现一个 10,000 次迭代的循环数学运算,但显然这种极其状况十分少见,大部分应用程序耗费的存储 gas 都远远大于指令 gas。
载荷 gas
截止本文编写的时刻,transaction.rs 中定义了一笔交易的最小外部 gas 老本为 1,500,000 内部单位(至多 15,000 octas),如果载荷大于 600 字节,那么每字节减少 2,000 外部 gas 单位(起码 20 octas)。交易容许的最大载荷为 65535 字节。因而在实践中,通常不怎么思考载荷 gas 的老本。
我的语雀原文在这里