关于gas费优化问题

关于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

猜你喜欢

转载自blog.csdn.net/weixin_43918614/article/details/130320811
gas