Skip to main content

基础 Gas 的工作原理

Aptos 交易默认收取基本汽油费,无论市场情况如何。 对于每笔交易,这个基础 gas数量基于三个条件:

  1. 说明。
  2. 储存。
  3. Payload。

交易所需的函数调用、分支条件语句等越多,其花费的指令 Gas 就越多。 同样,事务需要从全局存储中读取和写入的次数越多,它所花费的存储 Gas 就越多。 最后,交易 Payload 中的字节越多,成本就越高。

正如 优化原则 部分所述,存储 Gas 对基础 Gas 的影响最大。有关 Aptos Gas 模型的背景,请参阅 Aptos Gas 时间表的制定

Instruction gas 指令 Gas

基本指令 Gas 参数在 instr.rs 中定义,包括以下指令类型:

No-operation 无操作

参数含义
nop无操作

Control flow 控制流

参数含义
ret返回
abort中止
br_true执行条件真分支
br_false执行条件假分支
branch分支

Stack 栈

参数含义
pop从堆栈中弹出
ld_u8加载一个u8
ld_u64加载一个u64
ld_u128加载一个u128
ld_true加载一个true
ld_false加载一个false
ld_const_base加载常量的基本成本
ld_const_per_byte加载常量的每字节成本

Local scope 局部作用域

参数含义
imm_borrow_loc不可变借用
mut_borrow_loc可变借用
imm_borrow_field不可变借用字段
mut_borrow_field可变借用字段
imm_borrow_field_generic
mut_borrow_field_generic
copy_loc_base复制的基本成本
copy_loc_per_abs_val_unit
move_loc_base移动
st_loc_base

Calling 调用

参数含义
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_orOR: |
bit_andAND: &
xorXOR: ^
shl左移:<<
shr右移:>>

Boolean 布尔运算

参数含义
OR: ||
AND: &&
不是不是

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_global<T>()
imm_borrow_global_generic_base
mut_borrow_global_base可变借用的基本成本:borrow_global_mut<T>()
mut_borrow_global_generic_base
exists_base检查存在的基本成本:exists<T>()
exists_generic_base
move_from_base移动的基本成本:move_from<T>()
move_from_generic_base
move_to_base移动到的基本成本:move_to<T>()
move_to_generic_base

Vectors (类似数组)

参数含义
vec_len_base向量的长度
vec_imm_borrow_base不可变借用元素
vec_mut_borrow_base可变借用元素
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每个元素解包向量的基本成本

table.rsmove_stdlib.rsaptos-gas/src/ 中的其他各种源文件中定义了其他存储 Gas 参数。

储存 Gas

存储 Gas 在 storage_gas.move 中定义,在 storage_gas.md 中附带一个全面且内部链接的 DocGen 文件。

简而言之:

  1. initialize() 中,base_8192_exponential_curve() 用于生成指数曲线,随着利用率接近上限,每项和每字节成本迅速增加。
  2. 根据逐项和逐字节的利用率,通过 on_reconfig() 在每个 epoch 重新配置参数。
  3. 重新配置的参数存储在StorageGas中,包含以下字段:
领域含义
per_item_read从全局存储中读取项目的成本
per_item_create在全局存储中创建项目的成本
per_item_write覆盖全局存储中的项目的成本
per_byte_read从全局存储中读取一个字节的成本
per_byte_create在全局存储中创建一个字节的成本
per_byte_write在全局存储中覆盖一个字节的成本

在这里,item 是具有 key 属性的资源,或者是表中的条目,值得注意的是,每字节成本是根据项目的 entire 大小评估的。 如 storage_gas.md 中所述,例如,如果一个操作改变了具有五个其他 u128 字段的资源中的 u8 字段,则每字节的 gas 写入成本将占 (5128)/8+1=81(5 * 128 ) / 8 + 1 = 81 字节。

Vectors 向量

字节费用同样在向量上进行评估,它消耗 i=0n1ei+b(n)\sum_{i = 0}^{n - 1} e_i + b(n) 个字节,其中:

  • nn 是向量中的元素数
  • eie_i 是元素 ii 的大小
  • b(n)b(n) 是一个基本大小,它是 nn 的函数

请参阅 BCS 序列规范 以获取有关向量基本大小(技术上是 ULEB128)的更多信息,实际上它通常只占用一个字节,因此 100 个 u8 元素的向量占 100+1=101100 + 1 = 101 个字节. 因此,根据上述逐项读取方法,读取此类向量的最后一个元素被视为 101 字节读取。

Payload Gas

Payload Gas 在 transaction.rs 中定义,其中包含存储 Gas 与多个 Payload 和定价相关参数:

参数含义
min_transaction_gas_units交易的最低内部 Gas 单位,在执行开始时收取
large_transaction_cutoff大小(以字节为单位),超过该大小的交易将按每字节收取额外费用
intrinsic_gas_per_byte对于高于large_transaction_cutoff的 Payload,按字节收费的内部 Gas 单位
maximum_number_of_gas_units一笔交易的外部 gas 单位上限
min_price_per_gas_unit交易允许的最低汽油价格
max_price_per_gas_unit交易允许的最高 gas 价格
max_transaction_size_in_bytes最大事务 Payload 大小(以字节为单位)
gas_unit_scaling_factor内部 Gas 单位和外部 Gas 单位之间的换算系数

在这里,内部 Gas 单位被定义为源文件中的常量,例如 instr.rsstorage_gas.move,它们比外部 Gas 单位的粒度更细化了 gas_unit_scaling_factor 因子: 要将内部 Gas 单位转换为外部 Gas 单位,请除以gas_unit_scaling_factor。 然后,要将外部 Gas 单位转换为八进制,请乘以gas 价格,它表示每单位外部 Gas 的八进制数。

优化原则

单位和定价常量

在撰写本文时,transaction.rs 中的 min_price_per_gas_unit 定义为 aptos_global_constants::GAS_UNIT_PRICE(其本身定义为 100),其他值得注意的 [transaction.rs ] 常量如下:

常数价值
min_price_per_gas_unit100
max_price_per_gas_unit10,000
gas_unit_scaling_factor10,000

有关这些常量的含义,请参见 Payload gas

储存 Gas

在撰写本文时,initialize() 设置了以下最小存储 Gas 量:

数据风格操作符号最小内部 Gas
每件阅读rir_i300,000
每件创建cic_i5,000,000
每件wiw_i300,000
每字节阅读rbr_b300
每字节创建cbc_b5,000
每字节wbw_b5,000

最大数量是最小数量的 100 倍,这意味着对于 40% 或更低的利用率,总 gas 成本将是最小数量的 1 到 1.5 倍(请参阅 base_8192_exponential_curve() 以获取支持计算) )。 因此,就八进制而言,初始主网 gas 成本可以估算如下(内部 gas 除以比例因子,然后乘以最低 gas 价格):

操作操作最小八进制
每项阅读rir_i3000
每项创建cic_i50,000
每项写入wiw_i3000
每字节读取rbr_b3
每字节创建cbc_b50
每字节写入wbw_b50

到目前为止,最昂贵的每项操作是创建一个新项(通过 move_to<T>() 或添加到表中),其成本几乎是读取或覆盖旧项的 17 倍: c_i = 16.\overline{6} r_i = 16.\overline{6} w_i$。此外:

  • 每个项目的写入成本与读取成本相同:wi=riw_i = r_i
  • 但是,在每个字节的基础上,写入成本与创建成本相同:wb=cbw_b = c_b
  • 每字节写入和创建成本几乎是每字节读取的 17 倍:wb=cb=16.6rbw_b = c_b = 16.\overline{6} r_b
  • 每项读取的成本是每字节读取的 1000 倍:ri=1000rbr_i = 1000 r_b
  • 每个项目的创建成本是每个字节创建的成本的 1000 倍:ci=1000cbc_i = 1000 c_b
  • 每项写入的成本是每字节写入的 60 倍:wi=60wbw_i = 60 w_b

因此,对于读取和创建而言,每项操作的成本是每字节操作的 1000 倍,但写入的成本仅高 60 倍。

因此,在没有合理的经济动机从全局存储中解除分配(通过move_from<T>()或从表中删除)的情况下,最有效的存储 Gas 优化策略如下:

  1. 最小化每个项目的创作
  2. 尽可能跟踪未使用的项目并覆盖它们,而不是创建新项目
  3. 包含每个项目的写入尽可能少的项目
  4. 尽可能阅读而不是写作
  5. 最小化所有操作的字节数,尤其是写操作

指令 Gas

在撰写本文时,所有指令 Gas 操作都乘以 gas_meter.rs 中定义的 EXECUTION_GAS_MULTIPLIER,该值设置为 20。 因此,以下代表性操作假设 gas 成本如下(将内部 gas 除以比例因子,然后乘以最低 gas 价格):

操作最小八进制
表格添加/借用/删除框240
函数调用200
负载常数130
全球借100
读/写参考40
在堆栈上加载u12816
每字节表框操作2

(注意 per-byte table box 操作指令 gas 不计入存储 gas,单独评估)。

相比之下,读取一个 100 字节的项目至少需要 ri+100rb=3000+1003=3300r_i + 100 * r_b = 3000 + 100 * 3 = 3300 octas,大约是函数调用的 16.5 倍,并且通常,指令 gas 成本主要由储气成本。

然而,值得注意的是,在技术上仍然存在减少程序中函数调用次数的动机,但工程工作更有效地致力于编写旨在降低存储 Gas 成本的模块化分解代码,而不是尝试编写重复代码嵌套函数较少的块(几乎在所有情况下)。

在极端情况下,指令 Gas 可能远远超过存储 Gas ,例如,如果一个循环数学函数需要 10,000 次迭代才能收敛;但这又是一个极端情况,对于大多数应用,存储 Gas 对基础 Gas 的影响比指令 Gas 更大。

Payload Gas

在撰写本文时,transaction.rs 将每笔交易的最小内部 Gas 量定义为 1,500,000 个内部单位(至少 15,000 个八进制),每增加 2,000 个内部 Gas 单位(至少 20 个八进制)。字节用于大于 600 字节的 Payload,事务中允许的最大字节数设置为 65536。 因此,在实践中, Payload Gas 不太可能成为问题。