跳转至

Gas 优化指南(一Gas 机制原理

2980 个字 11 行代码 预计阅读时间 10 分钟

本篇是 Gas 优化系列的第一篇,聚焦于 Gas 机制的底层原理。

我们将从 Gas 的设计哲学出发,逐步拆解交易费用的构成、EIP-1559 定价模型、操作码成本分级、冷热访问机制,以及 SSTORE 的核心定价规则。理解这些基础知识,是后续进行 Gas 优化的前提。

一、Gas 是什么

Gas EVM " 燃料 ",用于衡量计算工作量。其设计目的主要包括:

  • 防止无限循环:必须付费才能执行代码,从机制上阻止恶意或无穷计算
  • 公平分配区块空间:区块空间有限,通过竞价机制分配执行权
  • 反映真实的计算成本:操作越复杂,对网络负担越重,对应 Gas 越高

EVM 的基本计费规则:每条 EVM 指令都有固定的 Gas 成本,一笔交易的总 Gas 消耗等于所有执行指令的成本之和。

以太坊中的计量单位:

单位 相当于 用途 科学计数法
1 ETH 1 Ether 账户余额、转账金额 10^0 ETH
1 Gwei 0.000000001 ETH 计算 Gas 价格 10^-9 ETH
1 wei 0.000000000000000001 ETH 最小单位 10^-18 ETH

wei(最小单位):合约与协议层统一使用整数运算,所有金额最终都以 wei 为单位存储和计算,避免浮点数精度问题。

gwei(最常用单位):这是日常使用最频繁的单位,主要用于表示 Gas 价格。当你在钱包(如 MetaMask)设置交易手续费时,看到的 "Gas Price" "Max Priority Fee" 通常就是以 gwei 为单位。例如,"Gas Price: 20 gwei" 意味着你愿意为每个计算步骤支付 20 gwei

ETH(标准单位):用于表示账户余额、转账金额、代币价格等宏观数值。我们平时说 " 我有一个以太币 ",指的就是 1 ETH

二、交易费用构成

一笔交易的 Gas 消耗分为两部分: Gas = Intrinsic Gas + Execution Gas

Intrinsic Gas(固有成本)

Intrinsic Gas 本质上是在为 " 交易进入 EVM 执行环境之前 " 的行为付费,它是交易本身的基础成本,与合约执行无关。包括:交易签名验证、nonce 校验、账户存在性检查、calldata 字节拷贝等。

组成部分 Gas 成本
基础费用 21,000
每字节零值 calldata 4
每字节非零值 calldata 16
创建合约额外费用 32,000

示例:一笔简单的 ETH 转账,calldata 为空,消耗 21,000 Gas

Execution Gas(执行成本)

合约代码执行过程中,每条指令的 Gas 成本累加。

EVM 不关心你在做什么业务,它只对三件事情收费:

  • 你输入了多少字节
  • 你执行了多少指令
  • 你是否改变了全网状态

在深入细节之前,先记住这个模型。以太坊上任何一笔交易的 Gas 消耗,都可以归入三个来源:

       存储成本
       (最贵)
          /\
         /  \
        /    \
       /      \
      /________\
 执行成本    数据成本
 (中等)   (按字节)
成本类型 来源 量级
存储成本 读写 Storage(SLOAD/SSTORE) 100 ~ 22,100 Gas
执行成本 运算指令(ADD/MUL/JUMP 等) 3 ~ 10 Gas
数据成本 Calldata(交易输入数据、Log(事件日志) Calldata:4 Gas/ 零字节,16 Gas/ 非零字节
Log:375 基础 + 375/topic + 8/ 数据字节

在大多数需要写入状态的业务合约中,存储读写往往是成本大头,所以我们默认先从 Storage 找热点。但纯读合约、签名验证合约、批量操作合约可能呈现不同的成本分布,具体问题具体分析。

三、EIP-1559 费用模型

2021 8 月伦敦升级后,在 EIP-1559 机制下,交易费用计算方式为:

交易费用 = Gas Used × (Base Fee + Priority Fee)

费用类型 说明
Gas Used 交易实际消耗的 Gas 数量
Base Fee 当前区块的基础费用(单位:wei/gas gwei/gas,由网络根据拥堵程度动态调整,会被销毁
Priority Fee 用户愿意支付给验证者的最高小费(单位:wei/gas gwei/gas,实际支付金额可能低于该值

需要注意:EIP-1559 ≠ Gas 优化,它只是定价模型,真正的优化发生在 EVM opcode 层。

Gas 优化发生在 opcode 层、storage 布局层、calldata memory 层,而 EIP-1559 只解决 " 每单位 Gas 多少钱 " 的问题。

Max Fee Max Priority Fee

为了保护用户,EIP-1559 引入了两个上限参数:

  • Max Fee Per Gas:用户愿意支付的最高总费用(包括基础费用和优先费用)
  • Max Priority Fee Per Gas:用户愿意支付的最高优先费用

实际支付的费用计算拆解如下:

第一步,定义有效小费:

effectivePriorityFee = min(MaxPriorityFee, MaxFee − BaseFee)

第二步,定义最终 Gas Price

effectiveGasPrice = BaseFee + effectivePriorityFee

最终费用:

txFee = gasUsed × effectiveGasPrice

这一步拆解在教学中非常重要,因为区块浏览器、RPC 返回值、EVM trace、receipt.effectiveGasPrice 全部都在使用这个中间变量。

上述过程合在一起的计算公式为:

实际费用 = Gas Used × min(Max Fee, Base Fee + min(Max Priority Fee, Max Fee - Base Fee))

多支付的部分会自动退还给用户。

动态调整机制

  • 区块利用率 > 50%Base Fee 上涨(最多 +12.5%
  • 区块利用率 < 50%Base Fee 下降(最多 -12.5%

EIP-1559 的设计是让区块在短期内可以 0.5x 2x 摆动,但长期稳定在 targetGasUsed 附近。

Base Fee 的调整依据是区块 gasUsed targetGasUsed 的偏离程度,而不是简单的百分比规则。

费用计算示例

假设:

  • Gas Used = 21,000(简单转账)
  • Base Fee = 30 gwei
  • Priority Fee = 2 gwei
  • Max Fee = 100 gwei
  • Max Priority Fee = 2 gwei

计算过程:

  • 实际优先费用 = min(2 gwei, 100 gwei - 30 gwei) = 2 gwei
  • 实际总费用每 Gas = min(100 gwei, 30 gwei + 2 gwei) = 32 gwei
  • 交易总费用 = 21,000 × 32 gwei = 672,000 gwei = 0.000672 ETH

其中:

  • 基础费用:21,000 × 30 gwei = 630,000 gwei(被销毁)
  • 优先费用:21,000 × 2 gwei = 42,000 gwei(支付给验证者)

Gas 优化的直接影响

从上述公式可以看出,降低 Gas Used 可以直接按比例减少交易费用。如果能将合约的 Gas 消耗从 100,000 降低到 80,000(减少 20%,那么用户的交易费用也会相应减少 20%

这就是为什么 Gas 优化如此重要——它直接影响到每一位用户的钱包。

四、操作码 Gas 成本分级

EVM 操作码的 Gas 成本反映了其计算复杂度:

等级 Gas 范围 典型操作
最便宜 2-3 ADD, SUB, LT, GT, POP
便宜 3-5 MUL, DIV, MLOAD, MSTORE
中等 10-30 JUMP, JUMPI, CALLDATALOAD
较贵 100-700 BALANCE, EXTCODESIZE, CALL(热访问)
昂贵 2100-2600 SLOAD, CALL(冷访问)
非常昂贵 5000-22100 SSTORE

关键观察:存储操作(SLOAD/SSTORE)是最昂贵的,因为它们涉及全网状态的读写。

SLOAD 贵,因为需要访问全局状态树;SSTORE 更贵,因为会改变共识状态,影响所有全节点。Gas 本质不是 CPU 时间,而是对全网负担的定价。

当你想判断「哪行代码最贵,用这个快速检查顺序:

  1. Storage 写入吗? → 第一优先级热点
  2. Storage 读取吗? → 第二优先级热点
  3. 有外部调用吗? → 检查是否可以合并或缓存
  4. 有循环吗? → 检查循环内是否有上述操作

先找 SSTORE,再找 SLOAD,再找外部调用,最后才优化计算指令。

五、冷 / 热访问机制(EIP-2929)

2021 年柏林升级引入了 " 访问列表 " 概念,区分冷访问和热访问。

同一笔交易中

  • 首次访问某个地址或存储槽 → 冷访问(贵)
  • 再次访问同一地址或存储槽 → 热访问(便宜)

冷访问贵的原因是 EVM 需要从底层数据库加载数据到内存,热访问便宜是因为数据已经在内存中了。

/ 热状态是交易级别的概念,不是区块级别。每笔新交易开始时,所有访问都重新变成「冷

storage slot 而言:

操作 Gas
cold SLOAD 2100
warm SLOAD 100

EIP-2929 本质是在惩罚 " 不可预测的状态访问 "。第一次访问贵,不是因为慢,而是因为节点无法提前缓存。

六、SSTORE 的核心定价(EIP-2200)

场景 Gas 成本 说明
0 → 非零 22,100 创建新存储槽
非零 → 非零(不同值) 5,000 修改现有值
非零 → 0 5,000,退款 清除存储槽
0/ 非零 → 相同值 100 无实际变化

SSTORE Gas 主要由「写入前后 slot 值的变化类型」决定,而不是 cold/warm。例如:

_status = 2; // ~5000 gas
_status = 1; // ~5000 gas

这两个都是非 0 → 非 0 SSTORE,即使第二次是 warm slot,依然是 ~5000 Gas,而不是 2900

概念 适用指令 决定因素
cold/warm SLOAD、slot 访问 本交易中是否首次访问该 slot
SSTORE 执行成本 SSTORE slot 的「原始值 → 新值」变化类型

SSTORE ≠ SLOAD + 写入,SSTORE 是一个原子语义操作,以状态变化为计价单位,而不是以指令执行为计价单位。

七、Gas 退款机制

退款机制的演进历程:

最早版本:非零→0 = 15,000 refund,refund cap = 1/2

EIP-2200refund cap 降为 1/5

EIP-3529:非零→0 refund 15,000 → 4,800,SELFDESTRUCT refund 移除

London 升级后的 EIP-3529 中,SSTORE 从非零值写为 0 时仍可获得 Gas 退款,但退款金额从原本的 15,000 Gas 大幅削减为 4,800 Gas。同时,单笔交易中可实际获得的退款总量仍受退款上限限制,即不超过该交易实际消耗 Gas(gasUsed)的 20%

EIP-3529 后的规则

操作 退款额度
SSTORE 清零(非零 → 0) 4,800
SELFDESTRUCT 不再有退款

退款上限:最多退还交易总 Gas 20%

为什么有退款上限?

历史上出现过「Gas Token」套利:在 Gas 便宜时写入大量存储,在 Gas 贵时清除获取退款。EIP-3529 将退款上限从 50% 降到 20%,基本堵死了这条路。

退款不是「返现,而是「抵扣」——它直接减少 Gas Used,但你不会在钱包里看到一笔退款。

不要为了退款而刻意清零。清零的 5,000 Gas 成本 + 4,800 退款 = 净成本 200 Gas,看起来很便宜。但如果下次还要用这个槽,你又要付 22,100 Gas 0 写入非零。只有当你确定这个存储槽「永远不会再用」时,清零才是划算的。

八、Gas 消耗预测心法

判断一段代码是否昂贵,只问三个问题:

  1. 是否写 storage
  2. 是否首次访问 slot
  3. 是否写入非零值

三者同时满足,必然是 Gas hotspot

再例如:for 循环本身几乎不贵,真正昂贵的是循环里的 SLOAD SSTOREmemory 扩展虽有二次项,但仍远便宜于 storage

这类经验规则的价值,远高于任何表格。

注意复杂性和可读性

Gas 优化通常会使代码变得更难读和更复杂。一个好的工程师必须在主观上权衡哪些优化是值得的,哪些不是。

小结

本篇介绍了 Gas 机制的核心原理:

  • Gas EVM 衡量计算工作量的燃料,交易费用由 Intrinsic Gas Execution Gas 两部分构成。
  • EIP-1559 定价模型通过 Base Fee Priority Fee 计算最终费用,但真正的 Gas 优化发生在 opcode 层。
  • 在所有操作中,存储读写(SLOAD/SSTORE)是最昂贵的,其中从 0 写入非零值的成本高达 22,100 Gas
  • 冷热访问机制和退款规则进一步影响实际成本。

下一篇我们将通过一个真实的链上实验,验证本篇介绍的 EIP-1559 费用计算规则,直观感受不同存储操作的 Gas 差异。

系列导航: