关于区块链:Aptos概念基础-Gas-费

3次阅读

共计 6667 个字符,预计需要花费 17 分钟才能阅读完成。

Aptos 链上的交易,在不思考市场供需的状况下,会收取一笔“根底 Gas 费”。它是由以下 3 局部组成:

  1. 指令
  2. 存储
  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.
简略说:

  1. 在初始化函数中(initialize()),有一个 base_8192_exponential_curve() 办法,用于生成一条指数曲线,当存储利用率靠近下限时,每元素和每字节的老本将疾速回升。
  2. 在每个世代中,都能够通过 on_reconfig() 来配置基于元素或字节利用率的参数。
  3. 这些参数存储在 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 优化策略如下:

  1. 尽量避免 per-item 模式的创立操作
  2. 放弃对未应用元素的追踪,尽量覆写它们,而不是创立新的元素
  3. 尽量避免 per-item 模式的写操作
  4. 尽量多读少写
  5. 尽量减少所以操作中的字节数,特地是写操作

    指令 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 的老本。

我的语雀原文在这里

正文完
 0