日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

中如何计算工龄_在Substrate中如何计算交易权重

發布時間:2023/12/10 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 中如何计算工龄_在Substrate中如何计算交易权重 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

建議在閱讀本文之前,先掌握關于Substrate中交易費用設計的基本概念。如果還沒有了解的童鞋,請移步: Kaichao:Substrate 區塊鏈應用的交易費用設計?zhuanlan.zhihu.com

讀完Substrate區塊鏈應用的交易費用設計的小伙伴,應該掌握:

  • 什么是權重(weight)
  • 交易費用包括:基本費用,字節費用,權重費用
  • 交易級別:Normal和Operational
  • 如何自定義一個權重計算方法: #[weight = FunctionOf(...)]
  • 如何使用固定權重值,#[weight = SimpleDispatchInfo::FixNormal(X)]

這篇文章會詳細介紹,如何合理地設計runtime中dispatchable function (用戶可調用的方法)的weight,學習這篇文章,希望大家可以掌握:

  • 如何寫出合格的weight document;
  • 如何使用benchmark幫助計算weight

0. 概要

作為一名runtime developer, 在weights和fees方面,我們應該:

  • 最小化runtime function的復雜度和占用的計算資源;
  • 精確地定義相關runtime function的權重(weight)

為了達到以上要求,我們應該:

  • 在寫runtime時,盡量多參考和follow優秀的設計和寫法
  • 在注釋中盡量寫清楚方法的時間復雜度
  • 計算這些方法在真實世界中的成本(benchmark),并把它和時間復雜度結合在一起思考

1. Follow Runtime Best Practices

https://github.com/paritytech/substrate

2. 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: Vec<T::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());<Members<T>>::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: Vec<u8>) { 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;<Payee<T>>::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#L123

https://github.com/paritytech/substrate/pull/4946

4. 如何計算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.rs

https://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::Benchmark<Block> for Runtime {fn dispatch_benchmark(module: Vec<u8>,extrinsic: Vec<u8>,steps: Vec<u32>,repeat: u32,) -> Option<Vec<frame_benchmarking::BenchmarkResults>> {use frame_benchmarking::Benchmarking;match module.as_slice() {b"pallet-balances" | b"balances" => Balances::run_benchmark(extrinsic, steps, repeat).ok(),b"pallet-timestamp" | b"timestamp" => 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/4875

benchmark 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/substrate?github.comhttps://substrate.dev/?substrate.dev

或者中文資料:

http://subdev.cn/?subdev.cn

參考資料

https://substrate.dev/docs/en/conceptual/runtime/weight

https://github.com/paritytech/substrate/pull/3157

總結

以上是生活随笔為你收集整理的中如何计算工龄_在Substrate中如何计算交易权重的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。