Pre:
Alex Roan: Hitchhiker’s Guide to the EVM 学习记录
有很多主题想讨论,但有一个最重要的概念:
Gas Golfing
gas golfing is the process of optimizing the existing functionality of your contract to to do the same things to be able to perform the same functions but just cheaper and more efficiently
高尔夫最终目的就是想打进球洞,过程中需要不断优化去减少杆数
gas优化也是一样,一个函数同样的功能怎么才能更便宜这里特指,优化对storage
的使用
What is Storage in Solidity?
定义:
storage is data that is persistent between transactions it’s stored in a contract for extended periods of time and it can be accessed and changed in future transactions
例子:
图中有个storage 变量,在其他编程语言中可能被称为instance variables
或class variables
but in solidity there is a catch so it is one of the most gas-intensive
things that a contract can play around with accessing it changing it is disproportionately expensive in comparison with what we associate with normal disk storage
不同之处在于,在别的编程语言中,持久化存储变量可能只是占用硬盘空间,但在solidity中持久化存储变量,在读写这类变量的数据都跟钱有很大关系。
Why is Storage Expensive?
中心化世界:
在中心化的世界中,持久化存储的特点:
-
单个硬盘
-
总成本低
-
易扩容
去中心化世界:
换到去中心化的世界中,存储不再是存在一个服务器的一个硬盘里,而是存在整个网络里的每一块硬盘上。
Evm里有两个很贵的op code
:
-
SSTORE
: store this piece of data in this storage slot -
SLOAD
: read this data from this specific slot any time that we set a storage variable in a function somewhere
省gas的五个方法:
非必要的话,不存Storage:
一般情况下,合约都不会再次使用这部分数据(转账记录),所以用event
代替Storage
。
对于gas的影响:
尽量用constant或immutable:
有不变量就尽量用constant
或immutable
用关键字constant
或immutable
会影响opcode slot
-
constant:constant value must be hard coded into the declaration rather than set in the constructor
-
immutable:set it in the constructor but as soon as that constructor is finished that value that we set for link can never change again只能仅仅一次在构造函数中设置值
区别在于初始化赋值的位置不一样。
gas影响:
用了不变量关键词后,读取一次数据对gas的影响。如果经常会读取到该变量,那么累计下来会节省很多gas
自己测试:
部署:
1 | // constant1.sol |
耗费gas:133322
1 | // constant2.sol |
耗费gas:104187
1 | // constant3.sol |
耗费gas:110824
读取:
-
constant1.sol
:耗费gas 23553 -
constant2.sol
:耗费gas 21420
明确表示Storage Variable:
后面写函数的时候,提醒自己在操作的是Storage的变量,会比较耗费gas
不要频繁读写:
状态变量在循环、多条件判断的时候,注意观察接触touch
状态变量的次数,读写超过两次(Slod
+SStore
)
做法:先把状态变量加载到内存里的临时变量,再去频繁读写
提及了EIP-2929里的Cold and Warm
的概念:读过的slot后,状态:cold
->warm
,下次读写会更便宜。
EIP2929, EIP2930 簡介
在实作层,EVM会维系一个本笔交易读取过所有交易的Set。每次有尚未读取过的slot时,就会先收取一笔CLOD_SLOAD_COST (2100),然后把这个slot加入这个set中,下次读写就会比较便宜。
对于已经读取过的Slot,再次写入的OpcodeSSTORE之gas cost为会降低为
5000 — COLD_SLOAD_COST (2100) = 2900
简单的说,单纯只操作一次
SSTORE
的总gas 会维持一样在5000 。但如果这个slot是之前有读过的,则写入的gas cost就会降低。近一步来说,一个x += 100,其实会变得更便宜:Pre-EIP-2929:
800 SLOAD + 5000 SSTORE = 5800
Post-EIP-2929:2100 SLOAD + 2900 warm SSTORE = 5000
gas影响;
-
warm slot
还是不如在内存里读取便宜 -
可能每次仅省了一丢丢
gas
,成千上万的tx
就会显现出gas优化
的好处
Pack Your Struct 打包结构体:
在EVM中,32是一个重要的数字,要记住!
评估一下第二个变量,如果存的内容较小,就可以定义长度小一点的。然后evm就会把两个变量压缩在slot1里面。
squeezes 挤挤总会有的
对变量适当的排序、选择适当的size,可以减少slot的使用
不知道有没类似的工具可以提示优化的