映射與數組:複製變數氣體消耗
我有一個契約,它有一個結構,包括地址和單位,以及一個結構的映射,就像這樣。
struct User { uint count; address userAddress; } mapping (address => User) users;
有一點我需要複製這個映射並清除舊映射的條目,比如這個虛擬碼:
users_backup = users; users = [];
當然這段程式碼是行不通的,所以我知道你可以像這樣循環遍歷它:
但這似乎很昂貴,尤其是在閱讀之後:
那麼我如何有效地做到這一點呢?
對於 10 萬使用者或 100 萬甚至 1 億條目的映射,這種循環方法是否可行?或者改變我的架構會更好嗎?
uint[] userCounts; address[] userAddresses;
我可以使用一系列數組而不是地圖。那麼,我是否能夠將一個數組複製到一個新變數中,並用更少的氣體在一兩行中清除舊變數?困難的是,即使數組也必須是變數數組,因為我事先無法知道有多少使用者會使用它。
你會怎麼做才能最大限度地減少氣體消耗?我只是不確定最好的模式是什麼,謝謝!
想出一個微不足道的例子並不容易。根據評論,我認為主要的擔憂大約是:
- 負責任地清理過期資訊並擷取為釋放不需要的儲存而提供的 gas 退款。
- 避免迭代。https://blog.b9lab.com/getting-loopy-with-solidity-1d51794622ad
- 在 O(1) 中,按使用者、按紀元、全域按紀元或全域全域訪問資訊。
您可能需要考慮更多路徑。您必須預測合約需要對自己的數據進行的所有查詢,並找出一種方法來訪問具有 O(1) 複雜性的答案。
這種方法是給你一些想法:
- 您將需要能夠輕鬆應對使用引用
mappings
of 的id 集的數據組織structs
。通常最好對存在的所有內容進行全域映射,並使用滿足合約自身查找邏輯所需的每種情況的指針列表。例如,每個 epoch 中存在的所有事務 ID 的全域列表和每個使用者的類似列表。您可以address(this)
用作“使用者”來表示全域集。您將在多個地方寫入密鑰,但只記錄一次。- 您將希望在結構的某些部分中添加另一個維度,以便在邏輯上將事物分組為時期(例如一年)。應該在邏輯上清除的內容進入 Epoch 層並自然地初始化為零。不應該每年重置的內容放在上面的圖層中,以便它看起來可以繼續前進。需要明確的是,數據必須保持靜止,因為大規模重組是不可行或不可取的。
- 如果您處理完數據,您應該成為一個好公民並進行垃圾收集。這也將幫助您節省使用系統的 gas 成本,因為每次使用者刪除非零值時,他們都會獲得 gas 退款。你必須在沒有迭代的情況下做到這一點。該問題的解決方案是使用現有的結構來檢查過期的 Epoch。如果數據仍然存在,則刪除一條記錄。每次使用者為任何目的提供氣體時,您都可以通過將修飾符附加到狀態更改函式來執行此操作。這應該不會令人反感,因為如果有要刪除的內容,使用者會獲得 gas 退款。
使用這些處理 CRUD 操作的 Set 庫,您可以使生活變得更輕鬆。https://github.com/rob-Hitchens/SetTypes。如果使用的方法不清楚,請查看本系列:https ://medium.com/robhitchens/solidity-crud-epilogue-e563e794fde
您可以將各種數據處理為庫 - 鍊錶、排序列表等。對於您的項目聽起來如此復雜的事情,我建議將這些問題與業務線應用程序分開,以減少重複並提高可讀性,並且可能可靠性。
建議的垃圾收集方法基於此處解釋的工作攤銷概念:https ://medium.com/@weka/dividend-bearing-tokens-on-ethereum-42d01c710657 。當然,目前的 Epoch 和合約的正常執行是首要關注的問題。如果清理過時的數據應該是無關緊要的,但是這樣做是一個好政策,你可以這樣做,這樣使用者就可以在清理過程完成之前獲得獎勵。
大致:
modifier garbageCollection { if(previousEpochStillExists) { // get to work removing one txn/record and all references to it } if(nothingLeft) // remove the epoch itsef } _; }
作為事後的想法,請仔細考慮最小化狀態儲存。您可以使用事件日誌使輸入永生(更便宜)。一個好的啟發式方法是將狀態儲存限制為合約自身內部邏輯所需的值。永遠不要因為客戶可能感興趣而進行記錄。有更有效的方法可以做到這一點。
希望能幫助到你。