这里我们接下来学习Solidity官方文档内嵌汇编的第二个示例学习VectorSum 。本文依旧以Solidity 0.8.7官方文档进行学习。
有了第一个示例的学习做铺垫,我们这里的学习就容易多了,什么也不说,直接上示例源码:
pragma solidity >=0.4.16 <0.9.0;
library VectorSum {
function sumSolidity(uint[] memory _data) public pure returns (uint sum) {
for (uint i = 0; i < _data.length; ++i)
sum += _data[i];
}
function sumAsm(uint[] memory _data) public pure returns (uint sum) {
for (uint i = 0; i < _data.length; ++i) {
assembly {
sum := add(sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
}
}
}
function sumPureAsm(uint[] memory _data) public pure returns (uint sum) {
assembly {
let len := mload(_data)
let data := add(_data, 0x20)
for
{ let end := add(data, mul(len, 0x20)) }
lt(data, end)
{ data := add(data, 0x20) }
{
sum := add(sum, mload(data))
}
}
}
}
这里要吐槽一下CSDN的markdown编辑器,当前还不支持Solidity代码块,平常我使用的Typora是支持的。 因为这里的代码比较简单,所以我们既不修改合约也不进行单元测试了,直接学习代码即可。
第一个函数sumSolidity 是传统的操作,当然也是推荐的操作。除非我们知道自己在做什么并且必须这么做,否则我们一般不使用内嵌汇编。 这里注释提到,因为边界检查无法移除,所以无法优化。
第二个函数:sumAsm 。对过注释我们知道,该函数移除了边界检查,那么边界检查在哪发生的呢?通过对比我们很容易得到结论,边界检查发生在sumSolidity 函数的_data[i] 操作中。那么是怎么避免的呢?通过直接读取相应的内存数据而不是通过索引访问来避免。 注意,动态数组也是一种动态大小类型的数据,所以变量_data 是存储的它的内存地址。并且它也有一个长度前缀,该前缀同样为32字节,和bytes 类型一样。这样,sum := add(sum, mload(add(add(_data, 0x20), mul(i, 0x20)))) 这行代码就很容易理解了。 add(_data, 0x20) 是数据地址加上长度前缀,这样就得到第一个元素的地址了。 mul(i, 0x20) 是第i 个元素的偏移量,因为一个uint256是32字节,所以这里是 i * 0x20 。其它,就算函数参数中的_data 是个uint8 数组,由于会拓展为256 位,所以这里的偏移量还是i * 0x20 。在EVM内部,不管是uint几,都是统一转换为uint256进行计算的。 第一个元素地址+ 偏移量,就得到了每个元素的地址,然后mload 读取该字节内容就是该元素的值了。 最后是在循环中累加sum ,很简单。
第三个函数更进了一步,将循环也放在内嵌汇编中去了。这里他使用的是地址作为索引进行循环遍历。 首先得到数组的长度,也就是_data 的第一个字节(长度前缀),再次注意这里_data 的值为它的起始内存地址,而不是真实内容。 接下来将_data 的地址加上0x20 (32),也就是得到第一个真实元素的内存地址。 接下来是for循环遍历,这里的语法稍微古怪一下,我们记住都好。它同样遵循初始条件,迭代条件和迭代操作这三个部分,不过每一部分都是单独一行。 初始条件为计算了最后一个元素结束后的内存地址,同样是第一个元素的内存地址加上整体偏移量。注意这里不是最后一个元素的内存地址,因为最后一个元素的内存地址的偏移量为(len-1) * 0x20 。在最后一个元素的内存地址再加了一个0x20 (最后一个元素内容),表示数组结束了。 迭代条件为一个lt 函数,它比较两个参数的值,如果第一个参数小于第二个参数,也就是只要元素的内存地址没有到结束地址,就返回1,否则返回0。这里可以看到1其实代表的是true,0代表的是false。 迭代操作一般用来更新索引,这里也不例外,相当于地址后移一个元素(32字节)。 循环体内的代码就很简单了,取出每个元素的值(mload ),然后进行累加。
好了,今天的学习就到此结了。由于水平有限,有什么写的不正确的恳请大家指正。
|