[Solidity] 變數順序對Gas的影響

Yi-Cyuan Chen
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。

優化策略

根據上面的特性,我們可以很容易的知道如何進行應對。

  1. 正確的排序與分組

將資料大小能剛好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上也是有效。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade