Solidity变量位置与GAS成本关系

简介: 如果你要优化Solidity合约的gas成本,变量的数据存储位置是第一个要考虑的因素。在这个教程中,我们将深入学习Solidity中的数据存储机制,包含以太坊虚拟机EVM的介绍、Solidity的三种数据存储位置的区别以及不同情况下跨区域数据赋值的gas成本分析与利用等内容。

如果你要优化Solidity合约的gas成本,变量的数据存储位置是第一个要考虑的因素。在这个教程中,我们将深入学习Solidity中的数据存储机制,包含以太坊虚拟机EVM的介绍、Solidity的三种数据存储位置的区别以及不同情况下跨区域数据赋值的gas成本分析与利用等内容。

用自己熟悉的语言学习 以太坊DApp开发Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart

1、以太坊虚拟机

在开始探讨Solidity的数据存储之前,我想先介绍下以太坊虚拟机的一些相关内容,以便更容易理解后续的部分。

EVM的内部结构大致如下图所示:

在这里插入图片描述

当我们安装以太坊客户端时,它其中就包含了EVM这个专门用于运行智能合约的轻量级操作系统。EVM的架构基于栈机器模型,这意味着其指令集是基于栈而非寄存器来运作的。EVM操作码清单在黄皮书中有描述,具体可查阅以太坊虚拟机操作码和指令参考手册

在EVM中指令的执行流程如下:当一个交易触发智能合约代码的执行时,就会实例化一个EVM,EVM的ROM载入了要调用的合约代码。程序计数器被清零,存储从合约账号对应的部分载入,内存清零,设置区块和环境变量,然后代码开始执行。

2、Solidity变量的数据存储位置

现在让我们回到memory关键字。从0.5.0版本开始,所有的复杂类型必须显式指定其存储的数据位置,有三种可选的数据位置:memory、storage和calldata。

注意:唯一可以省略数据位置声明的是状态变量,因为状态变量始终保存在账号的存储中。

storage/存储

  • 存储中的数据是永久存在的。存储是一个key/value库- 存储中的数据写入区块链,因此会修改状态,这也是存储使用成本高的原因。
  • 占用一个256位的槽需要消耗20000 gas
  • 修改一个已经使用的存储槽的值,需要消耗5000 gas
  • 当清零一个存储槽时,会返还一定数量的gas
  • 存储按256位的槽位分配,即使没有完全使用一个槽位,也需要支付其开销

memory/内存

  • 内存是一个字节数组,槽大小位256位(32字节)
  • 数据仅在函数执行期间存在,执行完毕后就被销毁
  • 读或写一个内存槽都会消耗3gas
  • 为了避免矿工的工作量过大,22个操作之后的单操作成本会上涨

calldata/调用数据

  • 调用数据是不可修改、非持久化的区域,用来保存函数参数,其行为类似于内存
  • 外部函数的参数必须使用calldata,但是也可用于其他变量
  • 调用数据避免了数据拷贝,并确保数据不被修改
  • 函数也可以返回使用calldata声明的数组和结果,但是不可能分配这些类型

3、Solidity数据位置与赋值成本的研究

如果你不期望合约代码出现不可预计的行为,重要的一点是理解数据位置的赋值是如何运作的。

下面列出了不同位置的变量间赋值的一些规则:

  • 在存储和内存(或调用数据)间的赋值将创建一个新的独立拷贝
  • 内存之间的赋值仅创建引用,这意味着对一个内存变量的修改会
    同时反应在其他引用相同数据的内存变量上
  • 从存储到局部存储变量的赋值,实际上只会给一个引用
  • 所有其他赋值通常导致产生新的数据拷贝。例如赋值给状态变量
    或位于存储的结构类型的局部变量成员时,即使局部变量只是一个
    引用,也会产生新的数据拷贝

下面让我们用remix debugger深入研究一下:

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.7.0;

contract DataLocationTest {
    
    uint[] stateVar = [1,4,5];
    
    function foo() public{
        // case 1 : from storage to memory
        uint[] memory y = stateVar; // copy the content of stateVar to y
        
        // case 2 : from memory to storage
        y[0] = 12;
        y[1] = 20;
        y[2] = 24;
        
        stateVar = y; // copy the content of y to stateVar
        
        // case 3 : from storage to storage
        uint[] storage z = stateVar; // z is a pointer to stateVar
        
        z[0] = 38;
        z[1] = 89;
        z[2] = 72;
    }
    
}

用上面的代码创建一个新文件,然后部署合约。现在试着调用函数,你将会在控制台看到交易的详细信息以及旁边的debug按钮。点击这个按钮:

在这里插入图片描述

这时应当可以看到调试器区域大致如下:

在这里插入图片描述

点击上图中红色标识的箭头,单步执行代码。

你应当注意到的第一件事,是存储载入了stateVar的内容,这正如我们之前在EVM部分提到的,当然,这里没有局部变量。

当你继续单步执行时,你应当会看到变量y出现在局部变量区域(Solidity Locals)。继续单步执行,你还会看到需要执行很多字节码来创建必要的内存空间、从存储中载入所有数据并将其拷贝到内存。这意味着需要支付更多的gas,因此从存储区域到内存区域的赋值非常昂贵。

现在让我们研究下第二种情况:从内存区域赋值给存储区域。例如当你修改完内存变量后,可能需要将修改存回存储区域。这时也会消耗许多gas。如果我们计算debugger中单步执行前后的剩余gas差,可以看到消耗了17083 gas。该操作用了4个SSTORE指令:第一个用于保存数组大小,消耗800gas,其他三个用于更新数组的值,每个消耗5000gas。

接下来让我们看看第三种情况:从存储区域赋值给存储区域。这一次会创建一个新的局部变量来保存stateVar的值。如果我们查看代码的执行过程,就会注意到Solidity做的就是将第一个存储槽位的地址推入栈,该槽位保存有数组长度。根据文档说明,对动态数组而言,槽的位置包含了数组的长度。

如果我们比较不同情况下将数据拷贝进内存的成本,那么根据上述情况(更新并拷贝回存储:21629 gas,创建引用并直接更新状态:5085gas),非常清楚的是第二种方案的成本要低得多。

但是如果我们要直接更新状态变量,例如:

stateVar[0] = 12;

这也是可行的,不过如果你要处理映射和嵌套的数据类型,使用存储指针会让代码可读性更强。


目录
相关文章
|
数据可视化 前端开发 搜索推荐
数字孪生可视化开发技术(ThingJS)学习笔记
数字孪生可视化开发技术(ThingJS)学习笔记
814 0
|
安全 区块链 网络架构
Celer cBridge 跨链桥接技术讲解
文章介绍了Celer Network推出的去中心化非托管资产跨链桥cBridge,它支持在30多个区块链或Layer2上进行130多种资产的跨链转移,具有快速、低成本和安全性高的特点,通过状态守卫者网络(SGN)保障交易安全,并采用xAsset和xLiquidity两种桥接模型,为流动性提供者提供丰厚的奖励。
221 1
Celer cBridge 跨链桥接技术讲解
|
存储 Linux 应用服务中间件
RedHat红帽认证---RHCSA
RedHat红帽认证---RHCSA
510 0
|
10月前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。
|
12月前
|
存储 NoSQL 数据挖掘
MongoDB应用案例
MongoDB应用案例
358 1
|
Shell Android开发
安卓scheme_url调端:在AndroidManifest.xml 中如何配置 Intent-filter?
为了使Android应用响应vivo和oppo浏览器的Deep Link或自定义scheme调用,需在`AndroidManifest.xml`中配置`intent-filter`。定义启动的Activity及其支持的scheme和host,并确保Activity可由外部应用启动。示例展示了如何配置HTTP/HTTPS及自定义scheme,以及如何通过浏览器和adb命令进行测试,确保配置正确无误。
|
JavaScript
vue中轻松搞掂鼠标气泡框提示框tip跟随
vue中轻松搞掂鼠标气泡框提示框tip跟随
|
开发框架 安全 数据库
解锁Django框架神秘面纱!从入门到实战,掌握这些技巧,让你的Web应用秒变高效神器!
【8月更文挑战第31天】Django 是 Python 的明星 Web 开发框架,以其高效、安全及可扩展性著称,适用于构建各类 Web 应用。本文从 Django 基础概念出发,介绍其 MTV 架构,涵盖模型(Model)、模板(Template)、视图(View)等核心组件,并通过示例代码展示实际应用。此外,还将探讨路由配置、管理界面及实战技巧,帮助读者全面掌握 Django,为高效 Web 开发打下坚实基础。
156 0
|
机器学习/深度学习 并行计算 算法
深度学习驱动的声音生成:FunAudioLLM的创新架构
【8月更文第28天】随着深度学习技术的发展,声音合成的质量得到了显著提升。本文将介绍 FunAudioLLM —— 一种基于深度学习的声音生成框架,旨在创造高质量、自然流畅的声音内容。我们将探讨 FunAudioLLM 的核心技术、训练流程及其实现细节,并提供一些示例代码。
303 0
|
传感器 编解码 算法
Open3D Surface reconstruction 表面重建
Open3D Surface reconstruction 表面重建
467 4