为何移除构造函数中的SLOAD操作可大幅节省Gas?附实测数据
合约部署Gas消耗差异的原因
我将构造函数中引用的状态变量改为直接传入数值常量,测试了合约部署时的Gas消耗差异:
- 部署
BeforeDemo合约消耗93915 Gas - 部署
AfterDemo合约消耗76288 Gas - 两者差值达17627
原以为差异仅来自一次SLOAD操作,但单次SLOAD的Gas成本远低于此,请问造成如此大差值的原因是什么?
测试代码
contract BeforeDemo { uint256 public constant INITIAL_SUPPLY = 10000 * (10 ** 18); constructor() { check(INITIAL_SUPPLY); } function check(uint256 supply) public { } } contract AfterDemo { constructor() { check(10000 * (10 ** 18)); } function check(uint256 supply) public { } }
原因分析
这俩合约的Gas差异根本不是来自SLOAD——你搞错了,public constant变量并不会触发运行时的SLOAD操作,真正的差异来自以下两点:
- 自动生成的Getter函数成本:
BeforeDemo里的INITIAL_SUPPLY是public修饰的常量,编译器会自动生成一个同名的public getter函数,这个函数的字节码会被加入到合约的运行时代码中。部署合约时,运行时代码的每字节需要消耗200 Gas,这部分额外的字节码带来了不小的Gas开销。而AfterDemo没有这个常量定义,自然也不会生成多余的getter函数。 - 常量值的存储成本:
INITIAL_SUPPLY的数值会被嵌入到合约字节码的只读区域(包括getter函数的逻辑里),部署时写入这部分数据到链上需要消耗Gas——每存储一个非零字节是68 Gas,零字节是4 Gas,这个32字节的数值会产生对应的存储成本。而AfterDemo里的数值只是作为构造函数调用的参数,直接嵌入构造函数的字节码中,不需要额外在运行时代码里存储这个值,也不会因为getter函数重复嵌入数值。
把这两部分成本加起来,就解释了为什么Gas差值远大于单次SLOAD的成本(当前SLOAD的Gas成本仅800左右,和17k的差值完全不是一个量级)。
内容的提问来源于stack exchange,提问作者Zoey
相关产品推荐
相关产品推荐

