About gas fee optimization
First, let's take a look at this code
// 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];
}
}
}
}
In this code, a total state variable is defined, and it is passed into the even number of the array and the number less than 99 to accumulate.
When we first run the sum method, we can see that the gas cost for the first execution is
28654
Now we start the first optimization: change memory to 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];
}
}
}
}
At this point, let's look at the gas fee that needs to be used. At this time, it is about 2000 less.
26909
Why is this so?
Let's compare the difference between calldata and memory
In Solidity, calldata
it refers to the storage location of the parameters of the function call. Rather, it ismemory
the storage location of temporary variables declared inside the function.
The reason why the usage calldata
ratio memory
saves gas costs is that calldata
it is read-only and cannot be modified during function execution. On the contrary, memory
the data in can be modified during the execution of the function, which means that if you use a memory
large data structure to store, each modification needs to consume more gas costs.
In this scenario, we can see that nums does not seem to need to be changed as an input parameter, so here we can directly use calldata.
Let's look inside the loop body again
We found that the state variable total will be reassigned in each cycle. This kind of operation at the level of the overall state variable is actually a waste of gas.
We might as well change the way of thinking, extract the total out of the loop, and set a variable in the method to copy the total to the memory, that is, we accumulate a variable in the memory every time, so it will not be written State variables. At the end of the recycle, the result is written to the state variable at one time.
code show as below:
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;
}
}
At this time, we can execute to see the consumption of gas cost, and save about 200 at this time
26698
Let's take a look at what else can be optimized.
It is not difficult to see that there is actually no need to assign values to isEven and isLessThan99 separately every time. We can directly merge them into the conditional judgment. The combined effect is as follows:
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;
}
}
At this time, the gas cost is saved by about 300
26380
Let's look at the code for(uint i = 0;i<nums.length;i+=1), i+=1 will actually copy the value of i to a temporary variable, then increment i, and finally Then return the value of the temporary variable to the expression. Is there a way to avoid creating temporary variables? The answer is yes: we just need to change to ++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;
}
}
At this time, the gas cost has been saved by more than 300
26008
In the same loop just now, we can see that the length of the array must be read in each loop, so that this method must be executed every loop, so since its length is constant, we might as well directly store it in cache variable. code show as below:
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;
}
}
At this time, the gas fee has been saved by more than 100
25973
Looking at the code inside the loop, we can actually copy the element nums[i] of the array in the loop body to the memory in advance, the code is as follows:
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;
}
}
At this time, the gas cost is saved by about 200
25811