Gas 优化 - 幂运算与连乘的 gas 消耗

  • Congroo
  • 更新于 2022-10-18 16:23
  • 阅读 2233

solidity文档中提到,如果幂运算的指数较小时,使用连乘会更节约gas,那这个临界值在哪呢,幂运算的指数到多少之后会比连乘节约gas呢?能节约多少呢,毕竟蚊子腿也是肉

solidity 文档中提到,如果幂运算的指数较小时,使用连乘会更节约 gas ,那这个临界值在哪呢,幂运算的指数到多少之后会比连乘节约 gas 呢?能节约多少呢,毕竟蚊子腿也是肉

在“checked” 模式下,幂运算仅会为小基数使用相对便宜的 exp 操作码。 例如 x**3 的例子,表达式 x*x*x 也许更便宜。 在任何情况下,都建议进行 gas 消耗测试和使用优化器。

计算方法

Gas 消耗计算方法参考:计算Solidity 函数的Gas 消耗。使用 Hardhat 进行测试。

1. 测试合约代码

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/Strings.sol";

interface IGasCostTest{
    function setX() external;
    function setY() external;
}

contract GasCostTest{
    uint256 public x = 2;
    uint256 public y = 2;

    function setX() external {
        x = x ** 9;
    }

    function setY() external {
        y = y * y * y * y * y * y * y * y * y;
    }
}

contract CalculateGasCost {
    string public report;
    IGasCostTest public TestAddress;

    constructor(address testAddress_){
        TestAddress = IGasCostTest(testAddress_);
    }

    function GasCost(
        string memory name,
        function() external fun
    ) public {
        uint256 u0 = gasleft();
        fun();
        uint256 u1 = gasleft();
        uint256 diff = u0 - u1;
        string memory s = report;
        report = string(abi.encodePacked(s, string(abi.encodePacked(name, " GasCost: ", Strings.toString(diff))), "\n"));
    }

    function GenReport() public {
        GasCost("setX ", TestAddress.setX);
        GasCost("setY ", TestAddress.setY);
    }
}

2. Hardhat 测试代码

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Test x ^ 9", function () {
    let ContractAddress, CalculateGasCostAddress;

    before('布署合约', async function () {
        const ContractInstance = await ethers.getContractFactory("GasCostTest");
        const CalculateGasCostInstance = await ethers.getContractFactory("CalculateGasCost");
        ContractAddress = await ContractInstance.deploy();
        CalculateGasCostAddress = await CalculateGasCostInstance.deploy(ContractAddress.address);
    });

    it("CalculateGasCost", async function () {
        await CalculateGasCostAddress.GenReport();
        await CalculateGasCostAddress.GenReport();
        const res = await CalculateGasCostAddress.report();
        console.log(res);
        const x = await ContractAddress.x();
        const y = await ContractAddress.y();
        expect(x).to.equal(y);
    });
});

3. 结果

  • 未使用 remix 优化器:
Test x ^ 3
setX  GasCost: 8446
setY  GasCost: 6202
setX  GasCost: 8492
setY  GasCost: 6202

Test x ^ 9
setX  GasCost: 8446
setY  GasCost: 8314
setX  GasCost: 9073
setY  GasCost: 8314

Test x ^ 10
setX  GasCost: 8446
setY  GasCost: 8666
setX  GasCost: 9073
setY  GasCost: 8666
  • 使用 remix 优化器
Test x ^ 3
setX GasCost: 8242 
setY GasCost: 5674 
setX GasCost: 8345 
setY GasCost: 5674

# 因为会导致 uint256 溢出,所以只执行一次
Test x ^ 30
setX GasCost: 8242 
setY GasCost: 8239

Test x ^ 31
setX GasCost: 8242 
setY GasCost: 8334

4. 位运算

# 未使用 remix 优化器:
Test 1 << 3
setX  GasCost: 8468
setZ  GasCost: 5520

Test 1 << 250
setX  GasCost: 8468
setZ  GasCost: 5529

# 使用 remix 优化器:
Test 1 << 3
setX GasCost: 8264 
setZ GasCost: 5500

Test 1 << 250
setX GasCost: 8264 
setZ GasCost: 5506

结论

  1. 在未使用 remix 优化器下,幂运算的指数小于 9 时使用连乘能节约一定的 gas;使用 remix 优化器时,幂运算的指数小于 31 时使用连乘能节约一定的 gas;
  2. 底数是 2 时,使用位运算会是个更好的选择;
  3. 连乘数量较多时,代码可读性会变差,使用时需斟酌。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Congroo
Congroo
0x0ecD...7A67
江湖只有他的大名,没有他的介绍。