建站要多少钱,泰安建设网站公司,app对接广告联盟,买卖友情链接建议在阅读本文之前#xff0c;先掌握关于Substrate中交易费用设计的基本概念。如果还没有了解的童鞋#xff0c;请移步#xff1a; Kaichao#xff1a;Substrate 区块链应用的交易费用设计zhuanlan.zhihu.com读完Substrate区块链应用的交易费用设计的小伙伴#xff0c;…建议在阅读本文之前先掌握关于Substrate中交易费用设计的基本概念。如果还没有了解的童鞋请移步 KaichaoSubstrate 区块链应用的交易费用设计zhuanlan.zhihu.com读完Substrate区块链应用的交易费用设计的小伙伴应该掌握 什么是权重(weight) 交易费用包括基本费用字节费用权重费用交易级别Normal和Operational如何自定义一个权重计算方法: #[weight FunctionOf(...)]如何使用固定权重值#[weight SimpleDispatchInfo::FixNormal(X)]这篇文章会详细介绍如何合理地设计runtime中dispatchable function (用户可调用的方法)的weight学习这篇文章希望大家可以掌握如何写出合格的weight document;如何使用benchmark帮助计算weight0. 概要作为一名runtime developer, 在weights和fees方面我们应该最小化runtime function的复杂度和占用的计算资源精确地定义相关runtime function的权重(weight)为了达到以上要求我们应该 在写runtime时尽量多参考和follow优秀的设计和写法 在注释中尽量写清楚方法的时间复杂度计算这些方法在真实世界中的成本(benchmark)并把它和时间复杂度结合在一起思考1. Follow Runtime Best Practiceshttps://github.com/paritytech/substrate2. Weights注释首先我们应该为在runtime中的dispatchable方法完善关于weight的注释。这不仅可以帮助我们确定weight值的设定也可以帮助我们更有效地优化代码。关于weight的注释结果以时间复杂度表示的结果展示例如O(A logA BlogC)
2.1 注释要写什么weight相关的注释应当要包含对runtime方法的执行成本有明显影响的部分。比如存储相关的操作 (read, write, mutate, etc.)Codec(Encode/Decode)相关操作(序列化/反序列化 vecs或者大的结构体)search/sort等成本高的计算调用其他pallet中的方法...2.2 举例分析对下面一段代码进行weight注释时的分析// Join a group of members.
fn join(origin) {let who ensure_signed(origin)?;let deposit T::Deposit::get(); // configuration constantlet sorted_members: VecT::AccountId Self::members();ensure!(sorted_members.len() 100, Membership Full);match sorted_members.binary_search(who) {// User is not a member.Err(i) {T::Currency::reserve(who, deposit)?;members.insert(i, who.clone());MembersT::put(sorted_members);Ok(())},// User is already a member, do nothing.Ok(_) Ok(()),}Self::deposit_event(RawEvent::Joined(who));
}
Storage和Codec操作访问存储是一个成本很高的操作所以我们应当写好注释并优化。每一个存储操作都应当结合相关的Codec复杂度写好注释。比如如果你需要从一个存储项中读取一个vec中的值weight应该这样写- One storage read to get the member of this pallet: O(M)
在这个例子中在存储中读取vec有一个codec复杂度O(M)因为要对member M 进行反序列化操作。稍后在module中可能还会把一个数据再写入存储中这也应该要有对应的注释- One storage write to update the members of this pallet: O(M)
Search, Sort 以及其他昂贵的计算如果在runtime中需要搜索或者排序的话同样也需要标注相应的复杂度。比如如果你在一个已经排序的list中执行搜索binary_search 操作的时间复杂度为O(logM), 如果是一个未经排序的list的话复杂度为O(M)。所以注释应当像下面这样写- Insert a new member into sorted list: O(logM)
调用其他pallet和trait如果你调用其他FRAME pallet的方法直接调用或者通过trait设置需要记录调用的那个方法的复杂度。比如如果你写的方法保留了一下余额在Balances里或者发送一个event通过System pallet你再注释里应该写上- One balance reserve operation: O(B)
- One event emitted: O(E)
最终的注释把以上的操作注释结合在一起我们就可以为一个方法写上完整的注释# weight
Key: M (len of members), B (reserve balance), E (event)
- One storage read to get the members of this pallet: O(M).
- One balance reserve operation: O(B)
- Insert a new member into sorted list: O(logM).
- One storage write to update the members of this pallet: O(M).
- One event emitted: O(E)Total Complexity: O(M logM B E)
# /weight
注意 在为方法写weight注释时可能引入了不同的参数记得吧每个参数都标记好。如果仔细看上面的样例代码就可以看到有两个操作的时间复杂度都是O(M)(存储读和写)但是整体的时间复杂度O并没有把这部分考虑进来。所以我们没法区分有同样复杂度的两个方法这意味着可以在这个方法里加入很多复杂度为O(M), O(logM)... 的操作但是并不会对最后的复杂度标记做任何更改weight(M, B, E) K_1 K_2 * M K_3 * logM B E
对这部分区别我们通过on-chain tests来衡量。3. 如何大致推断weight总结综合考虑时间复杂度和具体的操作参考目前FRAME中一些标志性的extrinsic比如transfer大致确定当前方法的weight数量级对weight有了更深的了解之后在测试之前我们可以给runtime方法先设定一个暂时的权重值。更多时候我们的extrinsics大概率都是normal transactions所以关于权重的声明大概率会是像下面这样#[weight SimpleDispatchInfo::FixedNormal(YOUR_WEIGHT)]
在深入探讨更加细致的方法之前我们可以简单地参考一下现有pallet中方法的weight给大家一个简单的参考System: Remark - 没有任何逻辑。就用权重值允许的最小值标注。/// Make some on-chain remark.
#[weight SimpleDispatchInfo::FixedNormal(10_000)]
fn remark(origin, _remark: Vecu8) { ensure_signed(origin)?;
}
Staking: Set Controller. - 一次固定复杂度的存储读写 500,000#[weight SimpleDispatchInfo::FixedNormal(500_000)]
fn set_payee(origin, payee: RewardDestination) { let controller ensure_signed(origin)?; let ledger Self::ledger(controller).ok_or(Error::T::NotController)?; let stash ledger.stash;PayeeT::insert(stash, payee);
}
Balances: Transfer. - 固定的时间复杂度 1,000,000#[weight SimpleDispatchInfo::FixedNormal(1_000_000)]
pub fn transfer(origin, dest: T::Lookup as StaticLookup::Source, #[compact] value: T::Balance
) { let transactor ensure_signed(origin)?; let dest T::Lookup::lookup(dest)?; Self as Currency_::transfer(transactor, dest, value, ExistenceRequirement::AllowDeath)?;
}
Elections: Present Winner - O(voters) 复杂度的计算加上一次写操作 (10,000,000)#[weight SimpleDispatchInfo::FixedNormal(10_000_000)]
fn present_winner( ... ) {//--lots-of-code--
}
如果想要在一个执行方法A的extrinsic最终会执行方法B那么会简单地在B的权重值的基础上加上10_000作为passthrough weight。 比如sudo中的sudo方法详见https://github.com/paritytech/substrate/blob/master/frame/sudo/src/lib.rs#L123https://github.com/paritytech/substrate/pull/49464. 如何计算weight给runtime中的某个方法定一个权重值是一件有些主观的事情思考的维度也有很多这里重点从技术角度介绍如何给runtime中的方法进行性能测试可以为确定方法的权重值多一些事实参考。一般来说想要给一个方法一个确定的权重值可以从以下几个角度考虑如果一个区块里只包括这一个方法的extrinsic你希望最多包括多少笔如果以balances.transfer作为基准那这个方法要用多少权重...从不同的侧重点触发可能得到的结果也会稍有不同。这里只介绍最后一种思考方式如果我们相信Substrate目前FRAME中的function weight是合理的话 。4.1 benchmark简单来说benchmark就是测试在指定的上下文中执行指定的runtime function 花费了多少时间(in nanosencond)。benchmark介绍Substrate中有关于benchmark的宏benchmarks!大家直接就可以使用。使用benchmarks!的整体结构如下benchmarks! {
_ {}
scenarioA {}: functionA(...)
scenarioB {}: functionA(...)
scenarioC {}: functionC(...)
}
像_, scenarioA, scenarioB, scenarioC这部分被称为arms可以简单地理解为性能测试中的场景/分支。可以针对一个runtime function构建多种场景比如最好情况、一般情况、最坏情况等而后面的functionA, functionB就是构建完场景上下文后具体执行的runtime function。如果该方法和arms重名的话可以用_代替省略。建议大家在性能测试时尽可能保守即考虑最坏情况来确定weight关于构建测试场景上下文大家可以参考substrate现有的做法比如输入参数可以构造成升序、降序、随机尽可能执行function中尽可能多的operations比如转转账时涉及到创建/清空地址...benchmark示例参考benchmark示例参考https://github.com/paritytech/substrate/blob/master/frame/balances/src/benchmarking.rshttps://github.com/paritytech/substrate/blob/master/frame/identity/src/benchmarking.rs暴露benchmark runtime api在impl_runtime_apis中给实现了benchmarking的pallet添加对应的benchmark接口impl_runtime_apis! {impl frame_benchmarking::BenchmarkBlock for Runtime {fn dispatch_benchmark(module: Vecu8,extrinsic: Vecu8,steps: Vecu32,repeat: u32,) - OptionVecframe_benchmarking::BenchmarkResults {use frame_benchmarking::Benchmarking;match module.as_slice() {bpallet-balances | bbalances Balances::run_benchmark(extrinsic, steps, repeat).ok(),bpallet-timestamp | btimestamp Timestamp::run_benchmark(extrinsic, steps, repeat).ok(),_ None,}}}
}
benchmark CLI最后一步为了使substrate CLI可以使用类似substrate benchmark 或者node-template benchmark这样的subcommand来执行指定pallet的性能测试我们还需要为cli添加对应的subcommand以及暴露benchmark host functions。因为目前node-template不是默认支持CLI中使用benchmark所以我们需要对node-template做一些必要的微调具体做法请参考https://github.com/hammewang/substrate-multisig/github.com之所以在substrate官方的node-template中没有集成benchmark也是Parity考虑后的结果https://github.com/paritytech/substrate/pull/4875benchmark CLI如何使用以multisig-template举例./target/release/node-template benchmark --chain dev --pallet multisig --extrinsic create_multisig_wallet --execution wasm --repeat 2 --steps 10这里会把测试结果写成一个csv方便后续统计。补充说明一下常用参数--execution wasm: 在wasm环境中执行性能测试这里支持的所有可能的参数[Native, Wasm, Both, NativeElseWasm]--pallet: 想要测试的pallet这里可以填pallet-multisig或者multisig--chain: 链运行的初始状态比如dev, local或者在chain_spec 自定义--steps: 从默认(1)开始执行的最多样本点数量--repeat: 每个样本点重复次数4.2 如何得到相对准确的weight在执行完上述命令会得到如下结果Pallet: multisig, Extrinsic: create_multisig_wallet, Steps: [10], Repeat: 2
u,extrinsic_time,storage_root_time
1,418000,39000
1,217000,29000
100,200000,28000
100,200000,29000
199,203000,29000
199,195000,27000
298,201000,28000
298,202000,28000
397,197000,28000
397,196000,29000
496,201000,28000
496,244000,28000
595,234000,31000
595,199000,28000
694,231000,32000
694,246000,29000
793,218000,31000
793,196000,28000
892,205000,29000
892,222000,29000
991,244000,32000
991,228000,31000根据第一行head可以看到第二列就是执行这个extrinsic在指定执行环境中所花费的时间。大家可以根据需要对其进行处理比如取平均值。然后大家可以在自己的机器上再执行一遍balances.transfer的benchmark我们选择了balances.transfer的weight作为基准参考。然后把两个性能测试的结果做一下对比大致就可以得到一个相对准确的weight值。再次友情提醒请大家注意性能测试时为了准确覆盖尽可能多的情况最好、最坏、一般这里贴一下Substrate目前已经有的部分benchmark结果补充目前Substrate中的benchmark仍然处于早期状态后期很可能会出现breaking changes。本文写于f41677d0, 想直接使用的童鞋请认准依赖版本。希望可以给大家提供到一些关于weight设定的基本概念以及可行但粗糙的实践方法。欢迎共同交流。最新动态github repo和substrate官方dev文档是更新最及时、也是知识结构最系统的地方值得star和收藏https://github.com/paritytech/substrategithub.comhttps://substrate.dev/substrate.dev或者中文资料http://subdev.cn/subdev.cn参考资料https://substrate.dev/docs/en/conceptual/runtime/weighthttps://github.com/paritytech/substrate/pull/3157