[Solidity] 變數順序對Gas的影響
Sep 9, 2018 · 3 min read
你也許不知道,Solidity宣告變數的順序也會影響Gas的消耗。
由於EVM的操作都是以32 bytes為單位進行,所以編譯器會嘗試在讀寫變數時,將小於32 bytes的變數打包成32 bytes一組來進行存取,以達到節省存取次數的目的。
不過編譯器並沒有足夠聰明,能自動將合約的變數做最佳化的分組。他會將固定大小的變數,依序每32 bytes為一組。例如下面的例子:
contract MyContract {
uint64 public a;
uint64 public b;
uint64 public c;
uint64 public d; function test() {
a = 1;
b = 2;
c = 3;
d = 4;
}
}
在執行test()時,雖然看起來寫入了四個變數,但是由於這四個變數加起來剛好是32 bytes,可以做一次性的寫入,所以實際上執行一個SSTORE,消耗20000 Gas。接著看下面的例子:
contract MyContract {
uint64 public a;
uint64 public b;
byte e;
uint64 public c;
uint64 public d; function test() {
a = 1;
b = 2;
c = 3;
d = 4;
}
}
中間插入了另一個變數,結果造成a, b, e和c會被分為一組,d獨立為一組。同樣的test()造成兩次寫入,消耗40000 Gas。最後再看一個例子:
contract MyContract {
uint64 public a;
uint64 public b;
uint64 public c;
uint64 public d; function test() {
a = 1;
b = 2;
// ... do something
c = 3;
d = 4;
}
}
這和第一個例子不同的地方是,在test()中的a和b寫入之後,做了一些其他的事情,最後才寫入c和d。結果這次會造成兩次的寫入,因為在執行do something的時候,編譯器判斷為打包的動作已經結束,就送出寫入。不過由於只有一組資料,所以這個程式結果會產生一新增和一次修改,總共會消耗25000 Gas。
優化策略
根據上面的特性,我們可以很容易的知道如何進行應對。
- 正確的排序與分組
將資料大小能剛好32 bytes的湊成一組,並且將時常一起更新的放一起。
bad
contract MyContract {
uint128 public hp;
uint128 public maxHp;
uint32 level;
uint128 public mp;
uint128 public maxMp;
}Good
contract MyContract {
uint128 public hp;
uint128 public mp;
uint128 public maxHp;
uint128 public maxMp;
uint32 level;
}我們假設hp和mp較常一起更新,而maxHp和maxMp較常一起更新,所以避免交錯排列。
2. 一次性讀寫
如上面的例子,盡量一次性讀寫。
bad
function test() {
hp = 1;
// ... do something
mp = 2;
}good
function test() {
// ... do something
hp = 1;
mp = 2;
}這個特性在struct上也是有效。