从阵列到固体阵列的复制的最佳实践是什么?

发布于 2025-01-20 10:22:56 字数 1413 浏览 3 评论 0 原文

我试图通过优化代码来节省气体。但是,我想知道,从阵列到固体阵列复制到阵列的最佳实践是什么。

我提出了两个选项。一个是通过指针复制(我猜),另一个正在使用循环。

TestOne.sol

contract TestContract {
    uint32[4] testArray;

    constructor(uint32[4] memory seeds) {
        testArray = seeds; // execution costs: 152253
    }

    function Show() public returns (uint32[4] memory) {
        return testArray;
    }
}

TestTwo.sol

contract TestContract {
    uint32[4] testArray;

    constructor(uint32[4] memory seeds) {
        for(uint i = 0; i < 4; i++) {
            testArray[i] = seeds[i];  // execution costs: 150792
        }
    }

    function Show() public returns (uint32[4] memory) {
        return testArray;
    }
}

I tested with Remix (Ethereum Online IDE), 0.8.13 Solidity Compiler with Enable optimization (200)

Discussion of test result

We can see that, Testone使用 152253 GAS 用于执行成本,而Testwo使用 150792 GAS 用于执行成本。

有趣的是, for-loop 使用的气体少于分配指针。在我的小想法中,循环比另一个要多。 (至少有分配 uint I ,替换4次,检查条件4次(是否 i&lt; 4 ),增加 i ++ 4次等)

我怀疑坚固性编译器的“优化”。但是,在没有“启用优化”的情况下进行相同的小实验后,它也会产生同样的结果,即循环使用较少的气体。 (198846 vs. 198464)

问题是

  1. 为什么上述事情发生了?

  2. 从数组到数组复制的最佳实践是什么?是否有任何复制功能,例如C ++'S std :: copy()

I am trying to save gas by optimize code. In a flash, however, I was wondered what is the best practice of copying from array to array in Solidity.

I present two option. One is copying by pointer (I guess) and the other is using for-loop.

TestOne.sol

contract TestContract {
    uint32[4] testArray;

    constructor(uint32[4] memory seeds) {
        testArray = seeds; // execution costs: 152253
    }

    function Show() public returns (uint32[4] memory) {
        return testArray;
    }
}

TestTwo.sol

contract TestContract {
    uint32[4] testArray;

    constructor(uint32[4] memory seeds) {
        for(uint i = 0; i < 4; i++) {
            testArray[i] = seeds[i];  // execution costs: 150792
        }
    }

    function Show() public returns (uint32[4] memory) {
        return testArray;
    }
}

I tested with Remix (Ethereum Online IDE), 0.8.13 Solidity Compiler with Enable optimization (200)

Discussion of test result

We can see that, TestOne used 152253 gas for execution costs, and TestTwo used 150792 gas for execution costs.

The funny thing is that, for-loop used less gas than just assigning pointer. In my little thought, for-loop would be more assembly codes than the other. (There would be, at least, assigning uint i, substitute 4 times, check conditions 4 times (whether i < 4), increase i++ 4 times etc.)

I suspected the "optimization" of solidity compiler. But, after doing same small experiment without "Enable optimization", it does same result that for-loop used less gas. (198846 vs. 198464)

The Question is

  1. Why do above things happened?

  2. What is the best practice of copying from array to array? Is there any copy function like C++'s std::copy() ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

仅冇旳回忆 2025-01-27 10:22:56

最佳实践是将数组从内存到存储,而无需循环浏览其项目。但是,在此示例中的合同优化是棘手的。 官方文档

如果您希望初始合同部署更便宜,并且以后的函数执行更加昂贵,请将其设置为 - Optimize-runs = 1 。如果您期望很多交易,并且不在乎更高的部署成本和输出尺寸,请设置 - 优化运行到一个高数字。

要在上面说明上面的说明,请考虑以下合同:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

contract TestLoop {
    uint32[4] testArray;

    function setArrayWithLoop(uint32[4] memory array) public {
        for(uint256 i = 0; i < array.length; i++)
            testArray[i] = array[i];
    }

    function setArrayWithoutLoop(uint32[4] memory array) public {
        testArray = array;
    }

    function show() public view returns (uint32[4] memory) {
        return testArray;
    }
}

contract NoLoop {
    uint32[4] testArray;

    constructor(uint32[4] memory array) {
        testArray = array;
    }

    function show() public view returns (uint32[4] memory) {
        return testArray;
    }
}

contract Loop {
    uint32[4] testArray;

    constructor (uint32[4] memory array) {
        for(uint256 i = 0; i < array.length; i++)
            testArray[i] = array[i];
    }

    function show() public view returns (uint32[4] memory) {
        return testArray;
    }
}

使用布朗尼

from brownie import TestLoop, NoLoop, Loop, accounts

def function_calls():
    contract = TestLoop.deploy({'from': accounts[0]})
    print('set array in loop')
    contract.setArrayWithLoop([1, 2, 3, 4], {'from': accounts[1]})
    print('array ', contract.show(), '\n\n')

    print('set array by copy from memory to storage')
    contract.setArrayWithoutLoop([10, 9, 8, 7], {'from': accounts[2]})
    print('array ', contract.show(), '\n\n')

def deploy_no_loop():
    print('deploy NoLoop contract')
    contract = NoLoop.deploy([21, 22, 23, 24], {'from': accounts[3]})
    print('array ', contract.show(), '\n\n')

def deploy_loop():
    print('deploy Loop contract')
    contract = Loop.deploy([31, 32, 33, 34], {'from': accounts[3]})
    print('array ', contract.show(), '\n\n')

def main():
    function_calls()
    deploy_no_loop()
    deploy_loop()

以下 Brownie-config.yaml

compiler:
  solc:
    version: 0.8.13
    optimizer:
      enabled: true
      runs: 1

给出以下输出:

Running 'scripts/test_loop.py::main'...
Transaction sent: 0x8380ef4abff179f08ba9704826fc44961d212e5ee10952ed3904b5ec7828c928
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0
  TestLoop.constructor confirmed   Block: 1   Gas used: 251810 (2.10%)
  TestLoop deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87

set array in loop
Transaction sent: 0xfe72d6c878a980a9eeefee1dccdd0fe8214ee4772ab68ff0ac2b72708b7ab946
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0
  TestLoop.setArrayWithLoop confirmed   Block: 2   Gas used: 49454 (0.41%)

array  (1, 2, 3, 4) 


set array by copy from memory to storage
Transaction sent: 0x0106d1a7e37b155993a6d32d5cc9dc67696a55acd1cf29d2ed9dba0770436b98
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0
  TestLoop.setArrayWithoutLoop confirmed   Block: 3   Gas used: 41283 (0.34%)

array  (10, 9, 8, 7) 


deploy NoLoop contract
Transaction sent: 0x55ddded68300bb8f11b3b43580c58fed3431a2823bf3f82f0081c7bfce66f34d
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0
  NoLoop.constructor confirmed   Block: 4   Gas used: 160753 (1.34%)
  NoLoop deployed at: 0x7CA3dB74F7b6cd8D6Db1D34dEc2eA3c89a3417ec

array  (21, 22, 23, 24) 


deploy Loop contract
Transaction sent: 0x1aa64f2cd527983df84cfdca5cfd7a281ff904cca227629ec8b0b29db561c043
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 1
  Loop.constructor confirmed   Block: 5   Gas used: 153692 (1.28%)
  Loop deployed at: 0x2fb0fE4F05B7C8576F60A5BEEE35c23632Dc0C27

array  (31, 32, 33, 34)

得出以下结论

  1. 当我们考虑合同功能调用呼叫优化时 : ,然后将内存使用到存储副本,,比循环复制更有效。比较使用的气体从函数 setArrayWithOutloop 和function setArrayWithloop
  2. 当我们考虑合同部署优化时,在结论1。最重要的中似乎存在相反的情况
  3. :合同构造人在合同寿命中仅被称为一次,即合同被部署到链条时。因此,最常见的是功能调用优化,而不是合同部署优化。这导致结论1。

The best practice is copy array from memory to storage without looping over their items. However contract optimization in this example is tricky. The official documentation says as follow:

If you want the initial contract deployment to be cheaper and the later function executions to be more expensive, set it to --optimize-runs=1. If you expect many transactions and do not care for higher deployment cost and output size, set --optimize-runs to a high number.

To illustrate above, consider following contracts:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

contract TestLoop {
    uint32[4] testArray;

    function setArrayWithLoop(uint32[4] memory array) public {
        for(uint256 i = 0; i < array.length; i++)
            testArray[i] = array[i];
    }

    function setArrayWithoutLoop(uint32[4] memory array) public {
        testArray = array;
    }

    function show() public view returns (uint32[4] memory) {
        return testArray;
    }
}

contract NoLoop {
    uint32[4] testArray;

    constructor(uint32[4] memory array) {
        testArray = array;
    }

    function show() public view returns (uint32[4] memory) {
        return testArray;
    }
}

contract Loop {
    uint32[4] testArray;

    constructor (uint32[4] memory array) {
        for(uint256 i = 0; i < array.length; i++)
            testArray[i] = array[i];
    }

    function show() public view returns (uint32[4] memory) {
        return testArray;
    }
}

and script written by using brownie:

from brownie import TestLoop, NoLoop, Loop, accounts

def function_calls():
    contract = TestLoop.deploy({'from': accounts[0]})
    print('set array in loop')
    contract.setArrayWithLoop([1, 2, 3, 4], {'from': accounts[1]})
    print('array ', contract.show(), '\n\n')

    print('set array by copy from memory to storage')
    contract.setArrayWithoutLoop([10, 9, 8, 7], {'from': accounts[2]})
    print('array ', contract.show(), '\n\n')

def deploy_no_loop():
    print('deploy NoLoop contract')
    contract = NoLoop.deploy([21, 22, 23, 24], {'from': accounts[3]})
    print('array ', contract.show(), '\n\n')

def deploy_loop():
    print('deploy Loop contract')
    contract = Loop.deploy([31, 32, 33, 34], {'from': accounts[3]})
    print('array ', contract.show(), '\n\n')

def main():
    function_calls()
    deploy_no_loop()
    deploy_loop()

with following brownie-config.yaml:

compiler:
  solc:
    version: 0.8.13
    optimizer:
      enabled: true
      runs: 1

which gives following outputs:

Running 'scripts/test_loop.py::main'...
Transaction sent: 0x8380ef4abff179f08ba9704826fc44961d212e5ee10952ed3904b5ec7828c928
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0
  TestLoop.constructor confirmed   Block: 1   Gas used: 251810 (2.10%)
  TestLoop deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87

set array in loop
Transaction sent: 0xfe72d6c878a980a9eeefee1dccdd0fe8214ee4772ab68ff0ac2b72708b7ab946
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0
  TestLoop.setArrayWithLoop confirmed   Block: 2   Gas used: 49454 (0.41%)

array  (1, 2, 3, 4) 


set array by copy from memory to storage
Transaction sent: 0x0106d1a7e37b155993a6d32d5cc9dc67696a55acd1cf29d2ed9dba0770436b98
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0
  TestLoop.setArrayWithoutLoop confirmed   Block: 3   Gas used: 41283 (0.34%)

array  (10, 9, 8, 7) 


deploy NoLoop contract
Transaction sent: 0x55ddded68300bb8f11b3b43580c58fed3431a2823bf3f82f0081c7bfce66f34d
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0
  NoLoop.constructor confirmed   Block: 4   Gas used: 160753 (1.34%)
  NoLoop deployed at: 0x7CA3dB74F7b6cd8D6Db1D34dEc2eA3c89a3417ec

array  (21, 22, 23, 24) 


deploy Loop contract
Transaction sent: 0x1aa64f2cd527983df84cfdca5cfd7a281ff904cca227629ec8b0b29db561c043
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 1
  Loop.constructor confirmed   Block: 5   Gas used: 153692 (1.28%)
  Loop deployed at: 0x2fb0fE4F05B7C8576F60A5BEEE35c23632Dc0C27

array  (31, 32, 33, 34)

Conclusions

  1. When we consider contract function call optimization, then usage of memory to storage copy, here is more info, is more gas efficient than copy by for loop. Compare gas used from function setArrayWithoutLoop and function setArrayWithLoop.
  2. When we consider contract deployment optimization it seems there is opposite situation to this in conclusion 1.
  3. Most important: Contract constructor is called only once in contract lifetime, just when the contract is deployed to chain. So the most often there is an function call optimization rather than contract deployment optimization. This leads to conclusion 1.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文