0x01 有趣的问题
有人提出这么个问题,下面哪种写法更省 gas
function mul(uint256 x) public pure returns (uint256) {
return x * x * x;
}
function exp(uint256 x) public pure returns (uint256) {
return x ** 3;
}
0x02 如何分析
两个写法最终的差别体现在是用运算符 " * " 还是 " ** " 上。
运算符 " * " 最终会用到 EVM 指令 MUL
运算符 " ** " 最终会用到 EVM 指令 EXP
对于 MUL 和 EXP 两个指令的消耗,我们可以查表:https://ethereum.org/en/developers/docs/evm/opcodes/
通过查表得知,MUL 指令一次消耗的 gas 是 5,两次 MUL 消耗的 gas 就是 10
EXP 指令的消耗有个动态计算公式
gas_cost = 10 + 50 * byte_len_exponent
" x ** 3 " 所用到 EXP 指令的消耗就是 10 + 50 * 1 = 60
但是不是这就意味着 "mul" 这个函数比 "exp" 这个函数更省 gas 呢?
0x03 实证
把代码在 remix 运行,当输入为 100 时,“exp” 的消耗是 1052
image.png
“mul” 的消耗是 1084
image.png
“exp” 函数比 "mul" 更省 gas,为啥呢?
这是因为 “ x * x * x" 这种写法需要更多的中间指令,如果对比 "x * x" 和 "x ** 2" 就会发现 "x * x" 要更省 gas 了。
但是,当我们把输入改为 1000 时,“exp“ 的消耗变为 1293
image.png
而 “mul” 的消耗仍然为 1084
image.png
这是为啥呢?
Solidity 的官方 blog https://blog.soliditylang.org/2020/12/16/solidity-v0.8.0-release-announcement/ 有这么一段话:
For bases up to 306, if the exponent is smaller than a hard-coded safe upper bound, it will use the exp opcode directly. If the base or the exponent is too large, it might fall back to the loop-based implementation.
明白了么?如果基数大小超过 306,solidity 会使用 MUL 而不是 EXP 指令,消耗了比直接用 MUL 更多的 gas。
如果我们不希望 solidity 为我们做这样的处理,可以使用 assembly:
function exp(uint256 x) public pure returns (uint256) {
assembly {
mstore(0x0, exp(x, 0x3))
return (0x0, 32)
}
}
网友评论