【Solidity】关于gas费优化与示例

文章通过示例展示了如何优化Solidity合约中的gas费用,包括将memory替换为calldata减少读写成本,将状态变量更新移出循环以降低状态变更的gas消耗,以及简化条件判断和循环逻辑来进一步节省gas。经过一系列优化,合约的gas费用显著下降。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于gas费优化问题

首先我们先来看一下这段代码

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GasGolf{

    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] memory nums) external{
        for(uint i = 0;i<nums.length;i+=1){
            bool isEven = nums[i] % 2 == 0;
            bool isLessThan99 = nums[i] < 99;
            if(isEven && isLessThan99){
                total += nums[i];
            }
        }
    }
}

在这段代码中定义了一个total的状态变量,并且将其传入数组的偶数以及小于99的数去进行累加。

当我们一开始运行sum方法的时候,我们可以看出第一次执行的gas费用为

28654

现在我们开始第一个优化:将memory改为calldata

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        for(uint i = 0;i<nums.length;i+=1){
            bool isEven = nums[i] % 2 == 0;
            bool isLessThan99 = nums[i] < 99;
            if(isEven && isLessThan99){
                total += nums[i];
            }
        }
    }
}

此时我们再看看所需要用到的gas费用,此时少了2000左右

26909

为什么会这样呢?

我们对比一下calldata和memory的区别

在Solidity中,calldata是指函数调用参数的存储位置。而memory是在函数内部声明的临时变量的存储位置。

使用calldatamemory更能节省gas费用的原因是,calldata是只读的,在函数执行期间不能被修改。相反,memory中的数据可以在函数执行期间被修改,这意味着如果你使用memory存储大数据结构,每次修改都需要消耗更多的gas费用。

在这个场景中我们可以看到似乎nums作为一个入参似乎并不需要被改变,所以这里我们直接用calldata即可。

我们再来看看循环体内部

我们发现这里面的total这个状态变量,在每一次循环里面都会将total里面的重新赋值,这种在整体状态变量层面的操作实际上是非常浪费gas的。

我们不妨换一种思路,将total抽离到循环外,方法内单独设置一个变量去将total拷贝到内存中去,也就是我们每次累加的是内存中的一个变量,这样并不会写入状态变量。最后再循环结束后再一次性的将结果写入到状态变量中。

代码如下:

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        for(uint i = 0;i<nums.length;i+=1){
            bool isEven = nums[i] % 2 == 0;
            bool isLessThan99 = nums[i] < 99;
            if(isEven && isLessThan99){
                _total += nums[i];
            }
        }
        total = _total;
    }
}

此时我们可以执行看看gas费用的消耗,此时又节省了200左右

26698

我们再观察一下里面还有什么地方可以优化的

不难看出里面其实isEven和isLessThan99其实没必要单独给他每一次都赋值,我们直接给他合并到条件判断里面就好,合并后的效果如下:

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        for(uint i = 0;i<nums.length;i+=1){
            if(nums[i] % 2 == 0 && nums[i] < 99){
                _total += nums[i];
            }
        }
        total = _total;
    }
}

此时gas费用又节省了300左右

26380

我们再看看for(uint i = 0;i<nums.length;i+=1)这一段代码,i+=1实际上会将 i 的值复制到一个临时变量中,然后对 i 进行递增操作,最后再将临时变量的值返回给表达式。那有没有可以避免创建临时变量的方法呢?答案是有的:我们只需要改为++i即可

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        for(uint i = 0;i<nums.length;++i){
            if(nums[i] % 2 == 0 && nums[i] < 99){
                _total += nums[i];
            }
        }
        total = _total;
    }
}

此时的gas费用又节省了300多

26008

同样是刚才的循环,我们可以看出每一次循环中都要读取出数组的长度,这样每次循环都要执行这个方法,那么既然他的长度是不变的,我们不如直接给他存入缓存变量中。代码如下:

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        uint len = nums.length;
        for(uint i = 0;i<len;++i){
            if(nums[i] % 2 == 0 && nums[i] < 99){
                _total += nums[i];
            }
        }
        total = _total;
    }
}

此时gas费用又省了100多

25973

再看看循环内部的代码,我们针对循环体内数组的元素nums[i]其实可以提前复制到内存中,代码如下:

contract GasGolf{
    uint public total;
    //[1,2,3,4,5,100]
    function sum(uint[] calldata nums) external{
        uint _total = total;
        uint len = nums.length;
        for(uint i = 0;i<len;++i){
            uint num = nums[i];
            if(num % 2 == 0 && num < 99){
                _total += num;
            }
        }
        total = _total;
    }
}

此时gas费用又节省了200左右

25811
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诗竹白芍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值