以太坊智能合约Solidity的优化

本文详细介绍了以太坊智能合约中Solidity的状态变量存储布局,包括静态大小变量、映射和动态数组的存储规则。同时讨论了如何通过优化存储变量的顺序和类型来减少 gas 消耗。还提到了 Solidity 的内存布局、编译器优化器的工作原理以及源映射的相关信息,为开发者提供了智能合约性能优化的指导和技巧。
摘要由CSDN通过智能技术生成

存储中状态变量

静态大小的变量(除映射和动态大小的数组类型之外的所有内容)在从位置开始的存储中连续布局0。根据以下规则,如果可能,将需要少于32个字节的多个项目打包到单个存储槽中:

• 存储槽中的第一项存储为低阶对齐。
• 基本类型仅使用存储它们所需的许多字节。
• 如果基本类型不适合存储槽的剩余部分,则将其移动到下一个存储槽。
• 结构和数组数据总是从一个新的槽开始并占据整个槽(但是根据这些规则,结构或数组中的项被紧密地打包)。

警告

使用小于32字节的元素时,合约的燃气使用量可能会更高。这是因为EVM一次运行32个字节。因此,如果元素小于该值,则EVM必须使用更多操作,以便将元素的大小从32字节减小到所需大小。

如果处理存储值,则使用缩小大小的参数是有益的,因为编译器会将多个元素打包到一个存储槽中,从而将多个读取或写入组合到单个操作中。处理函数参数或内存值时,没有固有的好处,因为编译器不会打包这些值。

最后,为了让EVM针对此进行优化,请确保您尝试订购存储变量和struct成员,以便可以紧密打包它们。例如,按照顺序声明存储变量,而 前者只占用两个存储槽,而后者占用三个。uint128, uint128, uint256uint128, uint256, uint128
结构和数组的元素彼此相继存储,就像它们是明确给出的一样。

映射和动态数组

由于其不可预测的大小,映射和动态大小的数组类型使用Keccak-256哈希计算来查找值或数组数据的起始位置。这些起始位置始终为完整堆栈插槽。

p 根据上述规则,映射或动态数组本身占据某个位置的存储槽(或者通过递归地应用该规则来映射映射或数组阵列)。对于动态数组,此槽存储数组中的元素数(字节数组和字符串是一个例外,见下文)。对于映射,插槽是未使用的(但需要使得两个相等的映射在彼此之后将使用不同的散列分布)。阵列数据位于keccak256§与对应于映射键的值 k位于其中是级联。如果该值再次是非基本类型,则通过添加偏移量来找到位置。keccak256(k . p).keccak256(k . p)

因此,对于以下合约代码段:

pragma solidity >=0.4.0 <0.6.0;

contract C {
struct s { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => s)) data;
}

的位置data[4][9].b是。keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1
bytes和string

bytes并且string编码相同。对于短字节数组,它们将数据存储在同样存储长度的槽中。特别是:如果数据最多为31字节长,则存储在高位字节(左对齐)和最低位字节存储中。对于存储长度或更长字节数据的字节数组,主插槽存储,数据按常规存储。这意味着您可以通过检查是否设置了最低位来区分短数组和长数组:short(未设置)和long(设置)。length * 232length * 2 + 1keccak256(slot)

注意

目前不支持处理无效编码的插槽,但将来可能会添加。

内存中的布局

Solidity保留四个32字节的插槽,具体字节范围(包括端点)的使用方法如下:

• 0x00- 0x3f(64字节):用于散列方法的临时空间
• 0x40- 0x5f(32字节):当前分配的内存大小(又名。空闲内存指针)
• 0x60- 0x7f(32字节):零时隙

可以在语句之间使用划痕空间(即在内联汇编中)。零槽用作动态存储器阵列的初始值,永远不应写入(空闲存储器指针0x80最初指向)。

Solidity总是将新对象放在空闲内存指针上,并且永远不会释放内存(这可能在将来发生变化)。

警告

Solidity中有一些操作需要一个大于64字节的临时存储区,因此不适合临时空间。它们将被放置在空闲内存指向的位置,但由于它们的生命周期很短,指针不会更新。存储器可能会或可能不会被清零。因此,不应期望空闲内存指向归零内存。

虽然使用msize来获得明确归零的内存区域似乎是个好主意,但是在不更新空闲内存指针的情况下非暂时使用这样的指针会产生不利的结果。

调用数据层

假定函数调用的输入数据采用ABI规范定义的格式。其中,ABI规范要求将参数填充为32字节的倍数。内部函数调用使用不同的约定。

合约构造函数的参数直接附加在合约代码的末尾,也是ABI编码。构造函数将通过硬编码的偏移量访问它们,而不是使用codesize操作码,因为当将数据附加到代码时,这当然会发生变化。

内部结构 - 清理变量

当值小于256位时,在某些情况下必须清除其余位。Solidity编译器用于在可能受到剩余位中潜在垃圾的不利影响的任何操作之前清除此类剩余位。例如,在将值写入存储器之前,需要清除剩余的位,因为存储器内容可用于计算哈希值或作为消息调用的数据发送。类似地,在将值存储在存储器中之前,需要清除剩余的位,因为否则可以观察到乱码值。

另一方面,如果紧接着的操作不受影响,我们不会清除这些位。例如,因为任何非零值被认为是true由JUMPI指令,我们不也被用作条件之前清理布尔值 JUMPI。

除了上面的设计原则,Solidity编译器在将输入数据加载到堆栈时清除输入数据。

不同类型有不同的规则来清理无效值:

类型是n个成员的枚举 ,有效值是0到n – 1 ,无效的值是例外。
类型是布尔,有效值是0或1,无效的值是1。
类型是签名整数,有效值是0或1,无效值是默认。
类型是无符号整数,有效值是高位归零,无效值是默认。

内部 - 优化器

Solidity优化器在汇编时运行,因此它可以并且也可以被其他语言使用。它将指令序列拆分为JUMPs和的基本块JUMPDESTs。在这些块中,分析指令并且对堆栈,存储器或存储的每个修改被记录为表达式,该表达式由指令和参数列表组成,这些参数基本上是指向其他表达式的指针。现在的主要思想是找到总是相等的表达式(在每个输入上)并将它们组合成表达式类。优化器首先尝试在已知表达式列表中查找每个新表达式。如果这不起作用,表达式将根据或等规则进行简化constant + constant = sum_of_constantsX * 1 = X。由于这是递归完成的,如果第二个因子是一个更复杂的表达式,我们也可以应用后一个规则,我们知道它总是会计算为一个。对存储和内存位置的修改必须删除有关存储和内存位置的知识,这些知识不为人知:如果我们先写入位置x然后写入位置y并且两者都是输入变量,第二个可能会覆盖第一个,所以我们实际上,在写完y之后,我不知道x处存储了什么。另一方面,如果表达式x-y的简化求值为非

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值