Description

You may not know that the order of declaration of variables will also affect the consumption of Gas.
Since the EVM operations are all performed in units of 32 bytes, the compiler will attempt to package the variables into a 32 bytes set for access, so as to reduce access times.
However, the compiler is not smart enough to automatically optimize the grouping of variables. It will group the statically-sized variables in groups of 32 bytes. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
contract MyContract {
uint64 public a;
uint64 public b;
uint64 public c;
uint64 public d;

function test() {
a = 1;
b = 2;
c = 3;
d = 4;
}
}

When you execute test(), it looks like you have stored four variables. Since the sum of these four variables is exactly 32 bytes, one SSTORE is actually executed. This only costs 20,000 Gas. Let’s see next case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

Another variable is inserted in the middle, which causes a, b, e and c to be grouped together, and d will be grouped independently. The same test() causes two writes and costs 40,000 Gas. Let’s see the other case:

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
}

The difference between this and the first example is that after a and b in test() are stored, something else is done, and finally c and d are stored. The result will cause two writes this time. Because when the do something is executed, the compiler determines that the packed action has ended, and then sends the write. However, since the second write is the same set of data, it is considered to be modified. A total of 25,000 gas will be consumed.

Optimization Strategy

According to the above principle, we can easily know how to deal with it.

Correct sorting and grouping

Group the data size by 32 bytes, and put together variables that are often updated at the same time.

Bad

1
2
3
4
5
6
7
contract MyContract {
uint128 public hp;
uint128 public maxHp;
uint32 level;
uint128 public mp;
uint128 public maxMp;
}

Good

1
2
3
4
5
6
7
contract MyContract {
uint128 public hp;
uint128 public mp;
uint128 public maxHp;
uint128 public maxMp;
uint32 level;
}

Here we assume that hp and mp are updated together more often, and maxHp and maxMp are updated together more often.

Access once

As in the example above, try to access it once.

Bad

1
2
3
4
5
function test() {
hp = 1;
// ... do something
mp = 2;
}

Good

1
2
3
4
5
function test() {
// ... do something
hp = 1;
mp = 2;
}

This rule is the same on struct.

Further Reading

Previoous Post: Solidity Gas Optimizations - Data Compression