区块链研究实验室-深入了解Solidity如何在以太坊上运行-第八节

破坏优化器

如果只有优化器可以一直运行良好。 让我们破坏它, 我们唯一的改变是我们使用辅助函数来设置存储变量:

pragma solidity ^0.4.11;

contract C {

uint64a;

uint64b;

uint64c;

uint64d;

function C() {

setAB();

setCD();

}

function setAB() internal {

a =0xaaaa;

b =0xbbbb;

}

function setCD() internal {

c =0xcccc;

d =0xdddd;

}

}

编译:

$ solc --bin --asm --optimize c-many-variables--packing-helpers.sol

组合输出太多了,我们将忽略大部分细节并专注于结构:

// Constructor function

tag_2:

// ...

// call setAB() by jumping to tag_5

jump

tag_4:

// ...

// call setCD() by jumping to tag_7

jump

// function setAB()

tag_5:

// Bit-shuffle and set a, b

// ...

sstore

tag_9:

jump// return to caller of setAB()

// function setCD()

tag_7:

// Bit-shuffle and set c, d

// ...

sstore

tag_10:

jump// return to caller of setCD()

我们现在有两个sstore而不是一个。 Solidity编译器可以在标记内进行优化,但不能跨标记进行优化。

调用函数可能会花费更多,而不是因为函数调用很昂贵(它们只是跳转指令),而是因为sstore优化可能会失败。

要解决这个问题,Solidity编译器需要学习如何内联函数,本质上是获取与不调用函数相同的代码:

a =0xaaaa;

b =0xbbbb;

c =0xcccc;

d =0xdddd;

如果我们仔细阅读完整的程序集输出,我们会看到函数setAB()和setCD()的汇编代码包含两次,代码的大小,耗费额外的gas来部署契约。 我们稍后会在了解合同生命周期时讨论这个问题。

为什么要破坏优化器?

优化程序不会跨标记进行优化,考虑“1 + 1”,如果在同一标签下,它可以优化为0x2:

// Optimize OK!

tag_0:

0x1

0x1

add

...

但如果指令不被标记分隔:

// Optimize Fail!

tag_0:

0x1

0x1

tag_1:

add

...

从版本0.4.13开始,目前该操作是正确的,未来版本可能会有不同。

再次破坏优化器

让我们看看优化器失败的另一种方式。 包装是否适用于固定长度的阵列?

pragma solidity ^0.4.11;

contract C {

uint64[4] numbers;

function C() {

numbers[] =0x0;

numbers[1] =0x1111;

numbers[2] =0x2222;

numbers[3] =0x3333;

}

}

同样有四个64位数字,我们希望使用一个sstore指令打包到一个32字节的存储槽中。

编译的程序集太长了。 让我们只计算sstore和sload指令的数量:

$ solc --bin --asm --optimize c-static-array--packing.sol | grep -E'(sstore|sload)'

sload

sstore

sload

sstore

sload

sstore

sload

sstore

即使此固定长度数组与等效结构或存储变量具有完全相同的存储布局,优化也会失败。现在需要四对sload和sstore。

快速查看汇编代码可以发现每个数组访问都绑定了检查代码,并在不同的标签下进行组织。但是标签边界会破坏优化。

3个额外的sstore指令比第一个更便宜:

sstore首先写入新位置需要20000gas。

sstore为后续写入现有位置花费5000gas。

所以这个特殊的优化失败花费我们35k而不是20k,额外75%。

结论

如果Solidity编译器可以计算出存储变量的大小,它只是在存储中一个接一个地将它们放在一起。如果可能,编译器将数据紧密地打包在32字节的块中。

总结一下到目前为止我们看到的包装行为:

存储变量:是的。

结构字段:是的。

固定长度数组:没有。

由于存储访问成本太高,您应该将存储变量视为数据库模式。在编写契约时,进行小型实验可能很有用,并检查程序集以确定编译器是否正确优化。

我们可以确定Solidity编译器将来会有所改进。所以目前我们还不能盲目相信它的优化器。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180813A07ZNW00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励