在 Solidity中使用值数组以降低 gas 消耗

本文讨论如何使用值数组(Value Array)替换引用数组(Reference Array)来减少 Solidity 智能合约的gas 消耗。

> * 原文地址:https://medium.com/coinmonks/value-arrays-in-solidity-32ca65135d5b 作者: [Julian Goddard](https://medium.com/@plaxion?source=post_page-----32ca65135d5b----------------------) > * 译文出自:[登链翻译计划](https://github.com/lbc-team/Pioneer) > * 译者:[Tiny熊](https://learnblockchain.cn/people/15) > * 本文永久链接:[learnblockchain.cn/article…](https://learnblockchain.cn/article/1381) > * 校对者: 无 ## 背景 我们Datona Labs在开发和测试Solidity数据访问合约(S-DAC:Smart-Data-Access-Contract)模板过程中,经常需要使用只有很小数值的小数组(数组元素个数少)。在本示例中,研究了使用值数组(Value Array)是否比引用数组(Reference Array)更高效。 ## 讨论 Solidity支持内存(memory)中的分配数组,这些数组会很浪费空间(参考 [文档](https://learnblockchain.cn/docs/solidity/types.html#arrays)),而存储(*storage*)中的数组则会消耗大量的gas来分配和访问存储。但是Solidity所运行的[以太坊虚拟机(EVM)](https://learnblockchain.cn/2019/04/09/easy-evm)有一个256位(32字节)机器字长。正是后一个特性使我们能够考虑使用值数组(Value Array)。在机器字长的语言中,例如32位(4字节),值数组(Value Array)不太可能实用。 我们可以使用值数组(Value Array)减少存储空间和gas消耗吗? > 译者注:机器字长 是指每一个指令处理的数据长度。 ## 比较值数组与引用数组 ### 引用数组(Reference Array) 在 Solidity 中,数组通常是引用类型。这意味着每当在程序中遇到变量符号时,都会使用指向数组的指针,不过也有一些例外情况会生成一个拷贝(参考[文档-引用类型](https://learnblockchain.cn/docs/solidity/types.html#reference-types))。在以下代码中,将10个元素的 8位uint `users` 的数组传递给`setUser`函数,该函数设置users数组中的一个元素: ```js 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`数组元素将被更改。 ### 值数组(Value Arrays) 值数组是以[值类型](https://learnblockchain.cn/docs/solidity/types.html#value-types)保存的数组。这意味着在程序中遇到变量符号,就会使用其值。 ```javascript 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 bytes32 值数组 Solidity 在 bytesX(X=1..32)类型中提供了一个部分值数组。这些字节元素可以使用数组方式访问单独读取,例如: ``` ... bytes32 bs = "hello"; byte b = bs[0]; require(bs[0] == 'h'); ... ``` 但不幸的是,在[Solidity 目前的版本](https://learnblockchain.cn/docs/solidity/types.html#index-7)中,我们无法使用数组访问方式写入某个字节: ``` ... bytes32 bs = "hello"; bs[0] = 'c'; // 不可以实现 ... ``` 让我们使用Solidity的 [using for](https://learnblockchain.cn/docs/solidity/contracts.html#using-for) 导入库的方式为bytes32类型添加新能力: ```js 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类型生成类似的库。 ### 测试一把 让我们导入该库并测试它: ```javascript 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[可用整型](https://learnblockchain.cn/docs/solidity/types.html#integers)匹配的固定长度的值数组: ``` 固定长度值数组 类型 类型名 描述 uint128[2] uint128a2 2个128位元素的值数组 uint64[4] uint64a4 4个64位元素的值数组 uint32[8] u...

  • 原文地址:https://medium.com/coinmonks/value-arrays-in-solidity-32ca65135d5b 作者: Julian Goddard
  • 译文出自:登链翻译计划
  • 译者:Tiny熊
  • 本文永久链接:learnblockchain.cn/article…
  • 校对者: 无

背景

我们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消耗吗?

译者注:机器字长 是指每一个指令处理的数据长度。

比较值数组与引用数组

引用数组(Reference Array)

在 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数组元素将被更改。

值数组(Value Arrays)

值数组是以值类型保存的数组。这意味着在程序中遇到变量符号,就会使用其值。

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 bytes32 值数组

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 &lt; elements);
        index = (elements - 1 - index) * bits;
        return bytes32((uint(va) & ~(0x0FF &lt;&lt; index)) | 
                        (uint(uint8(ev)) &lt;&lt; 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]    u...

在 Solidity中使用值数组以降低 gas 消耗插图

剩余50%的内容订阅专栏后可查看

  • 单篇购买 5学分
  • 永久订阅专栏 (90学分)
  • 发表于 2020-08-20 18:53
  • 阅读 ( 2107 )
  • 学分 ( 257 )
  • 分类:Solidity
  • 专栏:全面掌握Solidity智能合约开发

评论