Gas优化学习笔记1-乘法比加法贵

  • 老白
  • 更新于 2023-11-06 11:29
  • 阅读 752

在web2,只要功能实现了,代码写的好不好,最终用户是无法知道的。但是合约程序员就没这么幸福,代码写的不够,gas可能就非常高,导致的结果就是用户要多花钱。谁调用谁花钱,谁花钱谁知道。既然gas代表着明晃晃的钱,那我们就研究下怎么才能少花钱。本文用到的代码完整版:https://github

在web2,只要功能实现了,代码写的好不好,最终用户是无法知道的。

但是合约程序员就没这么幸福,代码写的不够,gas可能就非常高,导致的结果就是用户要多花钱。谁调用谁花钱,谁花钱谁知道。既然gas代表着明晃晃的钱,那我们就研究下怎么才能少花钱。

<br>

本文用到的代码完整版:

<https://github.com/rickwang9/GasOptimization>

<br>

打开 remix,新建一个合约Gas1.sol,写两个非常简单的函数:

  • lowGas 通过加法 10+10 返回20
  • highGas 通过乘法 10 * 2 返回20
    function lowGas()  public view returns(uint) {
        uint x=10;
        x = x + x;
        return x;
    }
    function highGas() public view returns(uint)  {
        uint x=10;
        x = x * 2;
        return x;
    }

两个函数都非常简单,但是消耗的gas却不一样!!! <br>

空口无凭,我需要实现逻辑,把调用每个函数消耗的gas都清晰的打印出来。

下面定义一个修改器modifier costGas

    modifier costGas(string memory name) {
        uint beforeGas = gasleft();
        _;
        uint afterGas = gasleft();
        console.log(name, concat("costGas: ", Strings.toString(beforeGas - afterGas)));
    }

核心是 gasleft(),返回值是uint,表示代码执行到这里,还剩多少gas。

<br>

另外,我还需要几个辅助方法

  • console.log
  • concat(string val1, string val2)
  • toString(uint value) <br>

console.log, solidity本身不支持打印,需要调用 hardhat的包,可以通过import导入

import "hardhat/console.sol";

<br>

concat,EVM对字符串的支持度不能说没有,只能非常低。原因以后说。像字符串的拼接,在其他语言都是最常见的,在solidity需要自己实现。

    function concat(string memory value1, string memory value2) public pure returns(string memory result){
        result = string(abi.encodePacked(value1, value2));
    }

<br>

Strings.toString(), solidity不支持字符串拼接,自然也不会支持整形转为字符串。还好有OpenZeppelin

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0/contracts/utils/Strings.sol";

<br>

准备好了这些,就可以打印前面两个函数的gas费用了, 修改代码,每个函数加上修改器constGas,传参是为了打印信息清晰一些:

    function lowGas() costGas('lowGas') public view returns(uint) {
        uint x=10;
        x = x + x;
        return x;
    }

    function highGas()  costGas('highGas') public view returns(uint)  {
        uint x=10;
        x = x * 2;
        return x;
    }

相信大家已经迫不及待看到运行结果了。别着急,耽误一点时间,看看import hardhat的openzeppelin都在哪里?看下图:

image.png

  • .deps/github/OpenZeppelin/openzeppelin-contracts/contracts/utils/math/Strings.sol
  • .deps/npm/hardhat/console.sol

<br>

好了,可以执行代码了。 <br>

执行lowGas函数, 返回20,正确

image-20231020155747796.png gas消耗 222

CALL
[call]from: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4to: Ga1.lowGas()data: 0x895...b3373
console.log:
lowGas costGas: 222

<br>

执行highGas函数, 返回20,正确

image-20231020155820620.png gas消耗 285

CALL
[call]from: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4to: Ga1.highGas()data: 0x62d...12eff
console.log:
highGas costGas: 285

<br>

打开 evm.codes, 这里列举了所有的opCode的gas fee

image-20231020160004005.png 可以清晰的看到, 加法gas=3, 乘法gas=5。

*所以: gas(10 + 10) < gas(10 2)**

<br>

等等, 是不是有点有点不对? lowGas:285, highGas:222, diff=63 ,而不是 5-3=2。

<br>

因为solc会把solidity编程成bytecode, EVM根据栈执行各种指令,来运行程序。 一行简单的计算,bytecode可能就是push,dup,swap,if,pop等等指令的组合。为了保证安全,bytecode包含了很多安全检查的指令,这些检查在你的代码里根本没有,不是你写的。但是bytecode里有,编译器自己加上的。

<br>

生成的bytecode,往往复杂重复且繁琐。所以想把复杂的bytecode反编译回源码,非常困难。

<br>

依然有很多多余的指令被solc生成,我们能否优化一下。答案是能。

<br>

看图,分成两步:

1) 编译界面,展开“>”符号

image-20231020163058637.png

<br>

2) 允许优化

勾选Enable optimization

输入 200

image-20231020163235674.png

解释一下,200 告诉优化器这个合约会被调用多少次。200表示多次调用,它就会优化bytecode,让本次调用的更节省gas。

<br>

我测试过:输入 200和3000,发现效果是一样的。uniswap的合约部署也是输入200.

<br>

然后看一下优化结果:

gas消耗 94

CALL
[call]from: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4to: Ga1.lowGas()data: 0x895...b3373
console.log:
lowGas costGas: 94

lowGas 返回20,正确

image-20231020155820620.png gas消耗 110

CALL
[call]from: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4to: Ga1.highGas()data: 0x62d...12eff
console.log:
highGas costGas: 110

<br>

优化前
lowGas 222
highGas 285
diff 63
优化后
owGas 94
highGas 110
diff 16

虽然 diff 依然没有达到2, 但是已经很好了。 <br>

solc生成的bytecode不可能和我们的代码完全一样,bytecode内部一定包含了solc认为有用的检查代码和必要的出入栈操作。 <br>

我们这里可以得出一个结论:

  1. gas(add) < gas(mul)
  2. 实际花费的gas要比 evm.code上看到的gas要多的多。

<br> <br>

<!--StartFragment-->

本文用到的代码完整版:

https://github.com/rickwang9/GasOptimization

<!--EndFragment-->

<!--StartFragment-->

引用

<https://learnblockchain.cn/column/1>

<!--EndFragment-->

点赞 1
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
老白
老白
江湖只有他的大名,没有他的介绍。