Solidity 中的 Gas 优化:哪些因素真正重要(以及哪些不重要)

本文介绍了 7 种实用的 Solidity Gas 优化技术,包括减少链上存储、使用 calldata 替代 memory、利用常量与不可变量、缓存存储读取、优先使用映射、采用自定义错误以及优化循环设计,旨在帮助开发者降低智能合约交互成本并提升系统性能。

Gas 优化曾经只是开发者担心的事。但现在情况已经不同了。

在 Web3 中,每一次交互都有成本。如果你的合约效率低下,用户会立即感受到 —— 他们为每笔交易支付更多费用。随着时间的推移,这种摩擦会不断累积。它会影响人们使用你产品的频率、他们是否留下来,甚至影响投资者如何评估你正在构建的东西。

到 2026 年,这一点变得更加明显。有更多的产品在竞争注意力,用户期望交易便宜,甚至在 Layer 2 上,低效率的合约仍然显得格格不入 —— 以一种糟糕的方式。

但 Gas 优化并不是要编写聪明的技巧或在每个地方压榨微小的收益。它实际上是关于从头开始设计高效的系统。

本指南介绍了在现实世界的 Solidity 开发中真正发挥作用的七种实用技术。

1. 最小化链上存储

如果有一件事需要记住,那就是:存储是昂贵的。

写入存储的 Gas 成本显着高于使用内存的成本。在许多合约中,这就是大部分不必要成本的来源。

示例(昂贵)

struct User {
    uint256 balance;
    uint256 timestamp;
}

mapping(address => User) public users;
function updateBalance(uint256 amount) public {
    users[msg.sender].balance = amount;
}

更高效的方法

与其存储所有内容,不如询问哪些内容真正需要留在链上。

event BalanceUpdated(address user, uint256 amount);

function updateBalance(uint256 amount) public {
    emit BalanceUpdated(msg.sender, amount);
}

这在实践中真正的意义

许多数据不需要永远存储在链上。日志(事件)通常就足够了,尤其是与索引器结合使用时。

一个简单的习惯对此有所帮助:在写入存储之前,停下来问一问 —— 这是否需要持久化在链上,还是仅仅被记录下来?

仅减少不必要的规范状态更改就可以大幅降低 Gas 成本。

2. 使用 calldata 代替 memory

这是那些悄悄产生巨大影响的小改变之一。

当你对函数输入使用 memory 时,Solidity 会创建数据的副本。该副本会消耗 Gas。而使用 calldata,函数会直接从交易输入中读取。

示例(效率较低)

function process(uint256[] memory data) public {
    // 逻辑
}

改进版

function process(uint256[] calldata data) external {
    // 逻辑
}

为什么这很重要

对于大型数组或批量操作,差异变得更加明显。你避免了不必要的复制,从而保持了较低的执行成本。

在实践中,一个好的规则是:如果数据不需要修改,请使用 calldata

3. 尽可能使用 constantimmutable

并非每个变量都需要留在存储中。

如果一个值永远不会改变,存储它就是一种浪费。Solidity 为你提供了更好的选择。

示例(效率较低)

uint256 public fee = 100;
address public owner;

constructor() {
    owner = msg.sender;
}

改进版

uint256 public constant FEE = 100;
address public immutable owner;

constructor() {
    owner = msg.sender;
}

底层发生了什么

常量和不可变量直接嵌入到合约字节码中。这意味着没有存储插槽,并且在部署和执行期间的 Gas 消耗更低。

这是一个简单的改变,但在一个大型系统中,它会迅速累积。

4. 缓存存储读取

从存储中读取并不是免费的 —— 重复读取甚至更糟。

示例(效率较低)

function getBalance() public view returns (uint256) {
    return balances[msg.sender] + balances[msg.sender];
}

改进版

function getBalance() public view returns (uint256) {
    uint256 balance = balances[msg.sender];
    return balance + balance;
}

为什么这很重要

每次存储读取都有成本。如果你多次读取相同的值,你每次都要为此付费。

将其在内存中缓存一次可以避免这种重复。这是一个很小的优化,但在真实的合约中非常常见 —— 尤其是在循环内部。

5. 优先选择映射(Mapping)而非数组(Array)(在合理的情况下)

数组看起来很简单,但它们的成本会迅速增加 —— 尤其是当你需要搜索它们时。

示例(基于数组)

address[] public users;

function findUser(address user) public view returns (bool) {
    for (uint i = 0; i < users.length; i++) {
        if (users[i] == user) return true;
    }
    return false;
}

基于映射的方法

mapping(address => bool) public isUser;

function checkUser(address user) public view returns (bool) {
    return isUser[user];
}

权衡

映射为你提供即时访问 (O(1)),而数组需要迭代。循环是增加 Gas 使用量甚至触及 Gas 限制的最快方式之一。

即便如此,当顺序或迭代很重要时,数组仍然是有意义的。关键是为工作选择正确的结构。

6. 使用自定义错误代替字符串

以字符串形式编写的错误消息出奇地昂贵。

示例(效率较低)

require(balance >= amount, "Insufficient balance");

改进版

error InsufficientBalance(uint256 balance, uint256 amount);
if (balance < amount) {
    revert InsufficientBalance(balance, amount);
}

为什么这效果更好

自定义错误使用更少的数据并减小合约大小。随着时间的推移,特别是在具有许多检查的合约中,这将带来显着的节省。

记住我以便更快登录

这已成为现代 Solidity 中的标准做法,原因很简单 —— 它更简洁、更便宜。

7. 刻意进行函数设计和循环

许多 Gas 效率低下的问题源于最初看起来不重要的小设计选择。

示例(效率较低)

function process(uint256[] memory data) public {
    for (uint i = 0; i < data.length; i++) {
        // 逻辑
    }
}

改进版

function process(uint256[] calldata data) external {
    uint length = data.length;

for (uint i = 0; i < length; ) {
        // 逻辑
        unchecked { i++; }
    }
}

这里改变了什么

  • 对于外部调用,externalpublic 更便宜
  • 缓存 length 避免了重复读取
  • 在安全的情况下,unchecked 移除了溢出检查

单独来看,这些都是很小的改进。合在一起,它们可以产生显著的差异 —— 尤其是在高频函数中。

安全考虑

过度优化是很诱人的,但这往往是出错的地方。

常见错误

  • 在没有适当验证的情况下使用 unchecked
  • 仅仅为了节省 Gas 而移除安全检查
  • 对关键逻辑进行过度优化
  • 使代码难以阅读和审计

更好的方法

从正确性开始。然后进行优化。

可读、结构良好的代码更容易审计和维护。在 Web3 中,这与节省几个单位的 Gas 同样重要。

真正有帮助的工具

没有测量的优化大多是猜测。

一些值得使用的工具:

  • Hardhat Gas Reporter
  • Foundry gas snapshots
  • Tenderly simulations
  • Remix gas analysis

这些工具可以帮助你识别真正的瓶颈,而不是追求微小的改进。

一种更好的 Gas 思考方式

很容易陷入微优化中。但更大的收益通常来自设计决策。

  • 减少不必要的规范状态更改
  • 选择高效的数据结构
  • 限制外部调用
  • 考虑长期使用,而不仅仅是部署

设计良好的系统自然具有更高的 Gas 效率。

结论

Gas 优化是那些容易被忽视的事情之一 —— 但一旦用户开始付出代价,就很难被忽视。

不优先考虑优化的项目往往会:

  • 收取更多费用
  • 随着时间的推移失去用户
  • 难以扩展

做得好的项目:

  • 保持低成本
  • 表现更好
  • 在拥挤的空间中脱颖而出

在这一点上,它已不再是可选的。它是预期的。

Ancilar 如何提供帮助

在 Ancilar,重点是从一开始就构建高效的系统,而不是事后才考虑。

这意味着超越功能性,审视合约在实际使用下的表现:

  • 大规模的 Gas 效率
  • 可持续的长期成本
  • 高性能的合约架构
  • 在真实负载下保持稳定的系统

目标不仅仅是让事情运转起来,而是让它们好用。

如果你正在构建 Web3 产品并希望你的合约高效、可扩展且准备好投入生产,你可以在这里联系:

https://www.ancilar.com/contactUs

  • 原文链接: medium.com/@ancilartech/...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ancilartech
ancilartech
江湖只有他的大名,没有他的介绍。