EVM虚拟机合约的执行过程

首先列出EVM虚拟机汇编指令集:https://gist.github.com/hayeah/bd37a123c02fecffbe629bf98a8391df

常用汇编指令: https://blog.csdn.net/qq_33733970/article/details/78572733 https://blog.csdn.net/baishuiniyaonulia/article/details/78504758

简单合约实例

编写一个简单合约

pragma solidity ^0.4.11;
contract C {
	uint256 a;
	function C(  ) {
		a = 1;
	}
}

使用remix查看汇编代码(点击detail查看):

在这里插入图片描述
可以查看BYTECODE、ABI、WEB3DEPLOY、RUNTIME BYTECODE 、ASSEMBLY等信息。
在这里插入图片描述

汇编分析

先看BYTECODE 。其中的object就是编译后的汇编指令。可以对照EVM虚拟机汇编指令集:https://gist.github.com/hayeah/bd37a123c02fecffbe629bf98a8391df 查看。下面的opcodes就是替换为对应指令后的结果。

再看WEB3DEPLOY,其中部署时data内容就是合约编译后的汇编指令。

接下来,我们重点看一下ASSEMBLY部分。从这部分,可以看到合约具体的执行过程。
在这里插入图片描述
我们先来回顾一下栈的操作过程(纯粹是按照过去的知识储备,与上面案例无关)。

假设有一个add操作,a=1+2。我们用[]符号来标识栈;用{}符号来标识合约存储器

往栈中压入值1 :[1]
王栈中压入值2:[2,1]
遇到add操作符
1和2出栈并且执行add计算,得到结果3
将3入栈 : [3]
将栈顶数据保存早0x0位置上, 清空栈:
栈:[]
存储:{0x0 → 3}
此时我们回到上面的案例,只看一下核心的 a=1 这个操作。

下图我将汇编和数字编码对应起来。5b6001600081905550 就是函数c的汇编代码:
在这里插入图片描述
具体执行过程:

//tag1 就是a=1的具体执行代码。
tag 1           function C() {\n      a = 1;...
  
//跳转到方法C   [5b]
JUMPDEST            function C() {\n      a = 1;...
  
//将1压入栈中  [60 01]
//执行结果:stack: [0x1]
PUSH 1          1
  
//将0压入栈中(这里是给a占个位置) [60 00]
//执行结果:stack: [0x0 0x1]
PUSH 0          a
  
//  复制栈中的第二项   [81]
//执行结果:stack: [0x1 0x0 0x1]
DUP2            a = 1
  
//  交换栈顶的两项数据  [90]
//执行结果:stack: [0x0 0x1 0x1]
SWAP1           a = 1
  
// 55: 将数值0x01存储在0x0的位置上. 这个操作会消耗栈顶两项数据。  [55]
//执行结果:stack: [0x1]
  //        store: { 0x0 => 0x1 }
SSTORE          a = 1
  
//丢弃栈顶数据   [50]
//执行结果:stack: []
  //        store: { 0x0 => 0x1 }
POP             a = 1

假设有两个变量:

pragma solidity ^0.4.11;
contract C {
	uint256 a;
	uint256 b;
	function C( ) {
		a = 1;
		b = 2;
	}
}

汇编代码:
在这里插入图片描述
ASSEMBLY:
在这里插入图片描述
可以看到他的执行过程就是按照参数顺序,依次执行的。

虚拟机的优化

将多个小字节的数据,存储到一个存储位置(32字节)中。
我们写这么一个合约

pragma solidity ^0.4.11;
contract C {
	uint128 a;
	uint128 b;
	function C() {
		a = 1;
		b = 2;
	}
}

编译之后的代码为:

.code
  PUSH 60           contract C {\n    uint128 a;...
  PUSH 40           contract C {\n    uint128 a;...
  MSTORE            contract C {\n    uint128 a;...
  CALLVALUE             function C() {\n      a = 1;...
  ISZERO            function C() {\n      a = 1;...
  PUSH [tag] 1          function C() {\n      a = 1;...
  JUMPI             function C() {\n      a = 1;...
  PUSH 0            function C() {\n      a = 1;...
  DUP1          function C() {\n      a = 1;...
  REVERT            function C() {\n      a = 1;...
tag 1           function C() {\n      a = 1;...
  JUMPDEST          function C() {\n      a = 1;...
  PUSH 1            1
  PUSH 0            a
  DUP1          a
  PUSH 100          a = 1
  EXP           a = 1
  DUP2          a = 1
  SLOAD             a = 1
  DUP2          a = 1
  PUSH FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF         a = 1
  MUL           a = 1
  NOT           a = 1
  AND           a = 1
  SWAP1             a = 1
  DUP4          a = 1
  PUSH FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF         a = 1
  AND           a = 1
  MUL           a = 1
  OR            a = 1
  SWAP1             a = 1
  SSTORE            a = 1
  POP           a = 1
  PUSH 2            2
  PUSH 0            b
  PUSH 10           b
  PUSH 100          b = 2
  EXP           b = 2
  DUP2          b = 2
  SLOAD             b = 2
  DUP2          b = 2
  PUSH FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF         b = 2
  MUL           b = 2
  NOT           b = 2
  AND           b = 2
  SWAP1             b = 2
  DUP4          b = 2
  PUSH FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF         b = 2
  AND           b = 2
  MUL           b = 2
  OR            b = 2
  SWAP1             b = 2
  SSTORE            b = 2
  POP           b = 2
  PUSH #[$] 0000000000000000000000000000000000000000000000000000000000000000            contract C {\n    uint128 a;...
  DUP1          contract C {\n    uint128 a;...
  PUSH [$] 0000000000000000000000000000000000000000000000000000000000000000         contract C {\n    uint128 a;...
  PUSH 0            contract C {\n    uint128 a;...
  CODECOPY          contract C {\n    uint128 a;...
  PUSH 0            contract C {\n    uint128 a;...
  RETURN            contract C {\n    uint128 a;...
.data
  0:
    .code
      PUSH 60           contract C {\n    uint128 a;...
      PUSH 40           contract C {\n    uint128 a;...
      MSTORE            contract C {\n    uint128 a;...
      PUSH 0            contract C {\n    uint128 a;...
      DUP1          contract C {\n    uint128 a;...
      REVERT            contract C {\n    uint128 a;...
    .data

上述代码执行结果就是讲a和b两个变量存储在一个存储位置上(32字节):
在这里插入图片描述
进行打包的原因是因为目前最昂贵的操作就是存储的使用:

sstore指令第一次写入一个新位置需要花费20000 gas
sstore指令后续写入一个已存在的位置需要花费5000 gas
sload指令的成本是500 gas
大多数的指令成本是3~10 gas
通过使用相同的存储位置,Solidity为存储第二个变量支付5000 gas,而不是20000 gas,节约了15000 gas。

字节码优化

上面已经将存储优化了。还可以将编译后的字节码优化一下。

在remix中启用优化:
在这里插入图片描述
优化后的字节码为(只列出tag1的部分):

tag 1           function C() {\n      a = 1;...
  JUMPDEST          function C() {\n      a = 1;...
  PUSH 0            a
  DUP1          a = 1
  SLOAD             a = 1
  PUSH 200000000000000000000000000000000            b = 2
  PUSH 1           
  PUSH 80          
  PUSH 2           
  EXP          
  SUB          
  NOT          
  SWAP1             a = 1
  SWAP2             a = 1
  AND           a = 1
  PUSH 1            1
  OR            a = 1
  PUSH 1           
  PUSH 80          
  PUSH 2           
  EXP          
  SUB          
  AND           b = 2
  OR            b = 2
  SWAP1             b = 2
  SSTORE            b = 2

可见,只有一次sstore命令。

不要小看这一次sstore指令的执行。在以太坊EVM执行中是需要消耗gas的。这样少一个sstore命令的执行,就节省了5000gas。

Gas 的使用

为什么ABI将方法选择器截断到4个字节?如果我们不使用sha256的整个32字节,会不会不幸的碰到不同方法发生冲突的情况? 如果这个截断是为了节省成本,那么为什么在用更多的0来进行填充时,而仅仅只为了节省方法选择器中的28字节而截断呢?

这种设计看起来互相矛盾…直到我们考虑到一个交易的gas成本。

每笔交易需要支付 21000 gas
每笔交易的0字节或代码需要支付 4 gas
每笔交易的非0字节或代码需要支付 68 gas
0要便宜17倍,0填充现在看起来没有那么不合理了。

方法选择器是一个加密哈希值,是个伪随机。一个随机的字符串倾向于拥有很多的非0字节,因为每个字节只有0.3%(1/255)的概率是0。

0x1填充到32字节成本是192 gas
431 (0字节) + 68 (1个非0字节)
sha256可能有32个非0字节,成本大概2176 gas
32 * 68
sha256截断到4字节,成本大概272 gas
32
4
ABI展示了另外一个底层设计的奇特例子,通过gas成本结构进行激励。

总结

EVM的编译器实际上不会为字节码的大小、速度或内存高效性进行优化。相反,它会为gas的使用进行优化,这间接鼓励了计算的排序,让以太坊区块链可以更高效一点。

我们也看到了EVM一些奇特的地方:

EVM是一个256位的机器。以32字节来处理数据是最自然的
持久存储是相当昂贵的
Solidity编译器会为了减少gas的使用而做出相应的优化选择
Gas成本的设置有一点武断,也许未来会改变。当成本改变的时候,编译器也会做出不同的优化选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐崇拜234

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值