本文讨论如何使用值数组(Value Array)替换引用数组(Reference Array)来减少 Solidity 智能合约的gas 消耗。
我们Datona Labs在开发和测试Solidity数据访问合约(S-DAC:Smart-Data-Access-Contract)模板过程中,经常需要使用只有很小数值的小数组(数组元素个数少)。在本示例中,研究了使用值数组(Value Array)是否比引用数组(Reference Array)更高效。
Solidity支持内存(memory)中的分配数组,这些数组会很浪费空间(参考 文档),而存储(storage)中的数组则会消耗大量的gas来分配和访问存储。但是Solidity所运行的以太坊虚拟机(EVM)有一个256位(32字节)机器字长。正是后一个特性使我们能够考虑使用值数组(Value Array)。在机器字长的语言中,例如32位(4字节),值数组(Value Array)不太可能实用。
我们可以使用值数组(Value Array)减少存储空间和gas消耗吗?
译者注:机器字长 是指每一个指令处理的数据长度。
在 Solidity 中,数组通常是引用类型。这意味着每当在程序中遇到变量符号时,都会使用指向数组的指针,不过也有一些例外情况会生成一个拷贝(参考文档-引用类型)。在以下代码中,将10个元素的 8位uint users
的数组传递给setUser
函数,该函数设置users数组中的一个元素:
contract TestReferenceArray {
function test() public pure {
uint8[10] memory users;
setUser(users, 5, 123);
require(users[5] == 123);
}
function setUser(uint8[10] memory users, uint index, uint8 ev)
public pure {
users[index] = ev;
}
}
函数返回后,users
数组元素将被更改。
值数组是以值类型保存的数组。这意味着在程序中遇到变量符号,就会使用其值。
contract TestValueArray {
function test() public pure {
uint users;
users = setUser(users, 5, 12345);
require(users == ...);
}
function setUser(uint users, uint index, uint ev) public pure
returns (uint) {
return ...;
}
}
请注意,在函数返回之后,函数的users参数将保持不变,因为它是通过值传递的,为了获得更改后的值,需要将函数返回值赋值给users变量。
Solidity 在 bytesX(X=1..32)类型中提供了一个部分值数组。这些字节元素可以使用数组方式访问单独读取,例如:
...
bytes32 bs = "hello";
byte b = bs[0];
require(bs[0] == 'h');
...
但不幸的是,在Solidity 目前的版本中,我们无法使用数组访问方式写入某个字节:
...
bytes32 bs = "hello";
bs[0] = 'c'; // 不可以实现
...
让我们使用Solidity的 using for 导入库的方式为bytes32类型添加新能力:
library bytes32lib {
uint constant bits = 8;
uint constant elements = 32;
function set(bytes32 va, uint index, byte ev) internal pure
returns (bytes32) {
require(index < elements);
index = (elements - 1 - index) * bits;
return bytes32((uint(va) & ~(0x0FF << index)) |
(uint(uint8(ev)) << index));
}
}
这个库提供了set()函数,它允许调用者将bytes32变量中的任何字节设置为想要的字节值。根据你的需求,你可能希望为你使用的其他bytesX类型生成类似的库。
让我们导入该库并测试它:
import "bytes32lib.sol";
contract TestBytes32 {
using bytes32lib for bytes32;
function test1() public pure {
bytes32 va = "hello";
require(va[0] == 'h');
// 类似 va[0] = 'c'; 的功能
va = va.set(0, 'c');
require(va[0] == 'c');
}
}
在这里,你可以清楚地看到set()函数的返回值被分配回参数变量。如果缺少赋值,则变量将保持不变,require()就是来验证它。
在Solidity机器字长为256位(32字节),我们可以考虑以下可能的值数组。
这些是以些Solidity可用整型匹配的固定长度的值数组:
固定长度值数组
类型 类型名 描述
uint128[2] uint128a2 2个128位元素的值数组
uint64[4] uint64a4 4个64位元素的值数组
uint32[8] uint32a8 8个32位元素的值数组
uint16[16] uint16a16 16个16位...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!