gas优化

gas优化: uint类型与evm字长

2022-11-02  本文已影响0人  梁帆

首先需要明确EVM的基础知识:

EVM采用了32字节(256bit)的字长,最多可以容纳2014个字,字为最小的操作单位。

在知道这点之后,有编程经验的人可能已经知道了,这跟内存对齐有关系。我们开始讲uint256、uint16、uint8类型在使用时的注意点。

1.uint8类型可能比uint256类型更加耗费gas

有以下两个合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract A {
    uint256 value = 100;
}

contract B {
    uint8 value = 100;
}

合约B的想法很好理解,即uint8类型的数值范围在0~255100在这个范围内,因此赋予uint8类型的value会更节省gas。让我么来部署试试:

合约 gas消耗
A 102626 gas
B 103088 gas

从表格中可以发现,B的gas消耗是要比A高的,这是因为:
EVM的存储字长是256位,因此不仅A中的value要分配1个字的长度,B中的value也要分配1个字的长度。此外由于B中value是uint8类型只有8位,所以EVM需要做额外的缩减操作,这样一来,gas就不减反增了。

2.其他uint类型节省gas的正确使用

有以下两个合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract A {
    uint256 value1 = 100;
    uint256 value2 = 100;
}

contract B {
    uint128 value1 = 100;
    uint128 value2 = 100;
}

按照上面的分析我们知道,A中需要分配2个字的长度,B中只需要分配1个字的长度,而且还不需要做缩减,因为两个128位类型加起来刚好等于1个字长,所以应该是B更节省gas。测试结果如下:

合约 gas消耗
A 128140 gas
B 105044 gas

结果和我们推理的一致。这里又有个新问题了,合约B中是两个uint128类型刚好凑成1个字长,如果是更小些的uint类型,凑不齐1个字长,那会是一个什么gas消耗情况呢?
于是我又做了2个uint64, 2个uint32,两个uint8的测试,得出的结果如下:

uint类型 占用字长 gas消耗
256 2 128140 gas
128 1 105044 gas
64 1 104455 gas
32 1 104161 gas
8 1 103940 gas

可以发现随着uint类型长度的缩减,两个该uint类型的值虽然还是只占1个字长,但是gas在同步地减少。

这跟我们想的似乎还是不太一样,拿两个uint128和两个uint8举例,两个uint128刚好凑满1个字,而两个uint8凑不满1个字,还得做缩减,不是应该更加消耗gas吗?

为了探求原因,我们做了debug,以下是两个合约部署时的汇编指令。
这是两个uint8类型的:

000 PUSH1 80
002 PUSH1 40
004 MSTORE
005 PUSH1 64
007 PUSH1 00
009 DUP1
010 PUSH2 0100
013 EXP
014 DUP2
015 SLOAD
016 DUP2
017 PUSH1 ff
019 MUL
020 NOT
021 AND
022 SWAP1
023 DUP4
024 PUSH1 ff
026 AND
027 MUL
028 OR
029 SWAP1
030 SSTORE
031 POP
032 PUSH1 64
034 PUSH1 00
036 PUSH1 01
038 PUSH2 0100
041 EXP
042 DUP2
043 SLOAD
044 DUP2
045 PUSH1 ff
047 MUL
048 NOT
049 AND
050 SWAP1
051 DUP4
052 PUSH1 ff
054 AND
055 MUL
056 OR
057 SWAP1
058 SSTORE
059 POP
060 CALLVALUE
061 DUP1
062 ISZERO
063 PUSH1 46
065 JUMPI
066 PUSH1 00
068 DUP1
069 REVERT
070 JUMPDEST
071 POP
072 PUSH1 3f
074 DUP1
075 PUSH1 54
077 PUSH1 00
079 CODECOPY
080 PUSH1 00
082 RETURN
083 INVALID
084 PUSH1 80
086 PUSH1 40
088 MSTORE
089 PUSH1 00
091 DUP1
092 REVERT
093 INVALID
094 LOG2
095 PUSH5 6970667358
101 INVALID
102 SLT
103 SHA3
104 SWAP9
105 PUSH29 ef37878a28ee38eb1289815a739b4d553cdc47c5ef777c6441080f4aa7
135 DUP4
136 PUSH5 736f6c6343
142 STOP
143 ADDMOD
144 SMOD
145 STOP
146 CALLER

这是两个uint128类型的:

000 PUSH1 80
002 PUSH1 40
004 MSTORE
005 PUSH1 64
007 PUSH1 00
009 DUP1
010 PUSH2 0100
013 EXP
014 DUP2
015 SLOAD
016 DUP2
017 PUSH16 ffffffffffffffffffffffffffffffff
034 MUL
035 NOT
036 AND
037 SWAP1
038 DUP4
039 PUSH16 ffffffffffffffffffffffffffffffff
056 AND
057 MUL
058 OR
059 SWAP1
060 SSTORE
061 POP
062 PUSH1 64
064 PUSH1 00
066 PUSH1 10
068 PUSH2 0100
071 EXP
072 DUP2
073 SLOAD
074 DUP2
075 PUSH16 ffffffffffffffffffffffffffffffff
092 MUL
093 NOT
094 AND
095 SWAP1
096 DUP4
097 PUSH16 ffffffffffffffffffffffffffffffff
114 AND
115 MUL
116 OR
117 SWAP1
118 SSTORE
119 POP
120 CALLVALUE
121 DUP1
122 ISZERO
123 PUSH1 82
125 JUMPI
126 PUSH1 00
128 DUP1
129 REVERT
130 JUMPDEST
131 POP
132 PUSH1 3f
134 DUP1
135 PUSH1 90
137 PUSH1 00
139 CODECOPY
140 PUSH1 00
142 RETURN
143 INVALID
144 PUSH1 80
146 PUSH1 40
148 MSTORE
149 PUSH1 00
151 DUP1
152 REVERT
153 INVALID
154 LOG2
155 PUSH5 6970667358
161 INVALID
162 SLT
163 SHA3
164 MOD
165 INVALID
166 GASLIMIT
167 INVALID
168 SWAP10
169 PUSH21 fd3bdb4da6ad6a18fbbcdd61c5ce445b1bec4d94ee
191 SGT
192 INVALID
193 INVALID
194 MULMOD
195 NUMBER
196 PUSH5 736f6c6343
202 STOP
203 ADDMOD
204 SMOD
205 STOP
206 CALLER

3.调节变量声明顺序节省gas

有以下两个合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract A {
    uint8 x = 100;
    uint256 y = 100;
    uint8 z = 100;
}

contract B {
    uint8 x = 100;
    uint8 z = 100;
    uint256 y = 100;
}

合约A、B中,它们仅仅是变量顺序不同。根据我们之前介绍EVM字长的基础知识,可以获知A合约占用了3个字长,而B合约占用了2个字长,因此B合约肯定更加节省gas,实验结果如下:

合约 gas消耗
A 154581 gas
B 129454 gas

和我们预知的一样。

上一篇下一篇

猜你喜欢

热点阅读