小B要从地球搬家到“链上星球”,搬家师傅递来一张报价单:扛一个箱子收一段路费,走楼梯加收,大件家具占用车厢另算,想要长期存放还得租仓库——每一项都是明码标价的“力气费”。链上世界的Gas正是这样的“力气费”:智能合约执行的每一个操作,小到一次加法、大到写入一段永久数据,都被标上了价格。最贵的是“永久仓库”——把数据存进区块链的存储区,要烧掉一大笔Gas;而临时在手上过一过的东西,却几乎不花钱。 聪明的搬家团队从不靠蛮力,而是靠会打包:把零碎物品塞进标准箱、能随身带的绝不进仓库、常取用的放在开放式货架。Gas优化的本质正是如此——在功能不变的前提下,把合约的“搬运量”压缩到最小,让每一笔交易的账单既快又省。这篇文章将跟着搬家师傅的每一步,从报价单、仓库租金、合并装箱、临时工作台到选箱工具,看看如何给智能合约“瘦身”,把Gas账单打到最低。

岗位关联

掌握Gas优化,意味着你能在DeFi、NFT等高频交易场景里为团队省下真金白银,是智能合约工程师从入门到进阶的核心必修课。

目录
  1. 可视化演示
  2. Gas机制基础:搬家公司的报价单
  3. EVM存储成本:仓库租金为何最贵
  4. 状态变量打包:把零碎物品合并装箱
  5. 计算与内存:临时工作台 vs 随身行李
  6. 数据容器选择:选箱子还是货架
  7. 实战优化工具:用尺子量着搬
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示EVM指令Gas成本对比、存储打包优化与Bitmap位图压缩技术,切换步骤查看不同Gas优化手段。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Gas成本模型 SSTORE新建:20000 Gas(最贵) ADD/MEM:仅3 Gas(最便宜) SLOAD热访问100 Gas vs 冷访问2100 Gas 存储写入是Gas消耗的主要来源,优先优化SSTORE

Gas机制基础:搬家公司的报价单

搬家公司的师傅不会凭感觉收费,而是掏出一张打印好的报价单:搬一个标准箱收几步路的钱,走一截楼梯加收多少,大件家具占用车厢空间另算。报价单上的每一项,都对应着一次真实的气力支出。

以太坊的EVM(以太坊虚拟机)也一样,对每条指令都有精确的Gas定价:普通算术运算只要3~5 Gas,读取内存3 Gas,但写入永久存储要几万Gas。Gas不是一种币,而是“节点执行一个单位的测量尺”。用户能自行设定Gas单价(gwei),但总共要烧多少Gas,取决于合约代码写得“累不累”。

EIP-1559引入了“基础费+优先费”双轨制。基础费根据上一个区块的拥堵程度自动上下浮动——区块塞得越满,下一块的基础费越高;使用率低则回落,目标是把平均使用率维持在50%左右。用户最终支付的总费用 = 用气量 × (基础费 + 优先费)。

报价单会随油价波动:货车多的时候,运费自然水涨船高——基础费就是链上的“油价”。看懂报价结构后,精明的用户知道何时出手更划算;但对开发者而言,更关键的是把包裹本身变轻。这就引出了最贵的“仓库租金”问题。

交易费用=用气量×(基础费+优先费)\text{交易费用} = \text{用气量} \times ( \text{基础费} + \text{优先费} )
公式解读:交易账单的“单价”由网络拥堵给出,“用量”则由合约代码的每一行操作累加而成。两头都想省钱,就得让操作变少。
  1. 用气量合约执行所有操作消耗的Gas总和,反映代码的“工作量”。
    用气量=∑每条指令指令Gas价格×指令执行次数\text{用气量} = \sum_{\text{每条指令}} \text{指令Gas价格} \times \text{指令执行次数}
  2. 基础费网络根据上一个区块的实际使用量、向50%目标回归的动态价格。
    基础费′=基础费+基础费×目标用气−实际用气目标用气×18\text{基础费}^{\prime} = \text{基础费} + \text{基础费} \times \frac{\text{目标用气} - \text{实际用气}}{\text{目标用气}} \times \frac{1}{8}
  3. 优先费用户额外支付的“小费”,激励验证者优先打包自己的交易。
    优先费=Gas单价−基础费\text{优先费} = \text{Gas单价} - \text{基础费}

EVM存储成本:仓库租金为何最贵

搬家师傅给小B算了笔账:随身工具袋里的螺丝刀,用一次就放回袋里,几乎不花钱;但要把旧沙发搬进永久仓库,每占一格都要按年付租金。链上的存储(Storage)就是那个永久仓库——合约的每一个状态变量都被锁在256位的格子里,放进去、改动它、哪怕只想看一眼,都要交钱。

存储读写之所以贵,是因为每个节点都要把这条数据永久记在自己的账本上并保持一致。EVM为此给出了严格的定价:读取存储(SLOAD)约100~2100 Gas,写入存储(SSTORE)则是几千到两万Gas起步。对比算术运算的3~5 Gas,差距是千倍级别的。

SSTORE的定价还分几种情况:向一个从没写过的空槽写入非零值(0→非零),要烧约20000 Gas,相当于“新租一格仓库”;把已有非零值改成另一个非零值,约5000 Gas,相当于“续约换物品”;把非零值改回零,虽然仍要付约2900 Gas,但协议会立即返还约4800 Gas,鼓励大家“退租”腾出空间。

仓库租金贵,自然要少租。有经验的师傅会把能合并的东西合成一个大箱,一次放一件和一次放四件,付的格子费却一样——这正是下一节“状态变量打包”要解决的事。

SSTORE成本≈{20000,0→非零(新租一格)5000,非零→非零(调换物品)2900,非零→0(清空,随后返费约4800)\text{SSTORE成本} \approx \begin{cases} 20000, & 0 \to \text{非零(新租一格)} \\[4px] 5000, & \text{非零} \to \text{非零(调换物品)} \\[4px] 2900, & \text{非零} \to 0 \text{(清空,随后返费约}4800\text{)} \end{cases}
公式解读:同样的SSTORE指令,因为数据“从哪来、到哪去”不同,价格相差4倍以上。新增存储槽位是全链最贵的单体操作。
  1. 新增槽 0 → 非零首次向空槽写入数据,要修改整棵存储树,约20000 Gas。
    20000=SSTORE0→非020000 = \text{SSTORE}_{0 \to \text{非}0}
  2. 修改槽 非零 → 非零覆盖已有数据,约5000 Gas。
    5000=SSTORE非0→非05000 = \text{SSTORE}_{\text{非}0 \to \text{非}0}
  3. 清空槽 非零 → 0执行付费约2900 Gas,紧接着协议返还约4800 Gas。
    返还≈4800>成本≈2900,净收益为负(省Gas)\text{返还} \approx 4800 > \text{成本} \approx 2900,\quad \text{净收益为负(省Gas)}

状态变量打包:把零碎物品合并装箱

师傅指着仓库的货架说:一个标准格层刚好放下四个小行李箱。如果你今天搬一个箱子,明天又搬一个,每次都占一个新格层,等于多付三倍租金。把四个小箱子叠进同一格层,租金立刻省下一大截——存储优化的第一课,就是学会“合并装箱”。

Solidity的存储模型正是“格子层”:每个存储槽(Storage Slot)固定256位(32字节)。编译器会把连续声明的、大小不足256位的状态变量(如bool、uint8、uint128)自动“塞”进同一个槽。塞得越满,占用的槽位越少,后续的SSTORE和SLOAD成本就越低。

例如一个玩家信息结构体包含 uint8 level; uint8 hp; uint16 bagSlots; uint8 mapId;——总位宽只有8+8+16+8=40位,编译器可以把它压缩进1个槽,读取四个字段时也只需要访问一次存储树节点。但若在中间插入一个256位的 uint256 score;,Score 需要独占一个整槽,其余成员就必须另占一个槽——总槽数从1变成2,Gas账单立刻翻倍。

师傅的装箱口诀是:“小件挨着小件放,大件单独占一层,顺序排好再搬车。”打包顺序看似只是代码排版,实际上每一格被浪费的仓库空间,都在为Gas账单默默充值。

打包后槽数=⌈∑连续小类型位宽256⌉\text{打包后槽数} = \left\lceil \frac{\sum \text{连续小类型位宽}}{256} \right\rceil
公式解读:存储槽是32字节的“标准格层”,所有塞进同一格层的变量共享一个SSTORE的“上架费”。向上取整告诉我们要么占用、要么浪费,中间没有第三种情况。
  1. 计算总位宽把所有可打包的小类型成员位宽相加。
    ∑位宽=8+8+16+8=40\sum \text{位宽} = 8 + 8 + 16 + 8 = 40
  2. 除以256并向上取整判断需要几个标准槽。
    ⌈40256⌉=1 个槽\left\lceil \frac{40}{256} \right\rceil = 1 \text{ 个槽}
  3. 插入大变量的变化uint256 占用一个整槽,其余成员再占一槽,总数变2。
    ⌈40256⌉+1=2 个槽\left\lceil \frac{40}{256} \right\rceil + 1 = 2 \text{ 个槽}

计算与内存:临时工作台 vs 随身行李

搬家师傅手上提着两个工具袋:一个是随身的“临时口袋”(memory),里面的螺丝刀、扳手用过就丢,不额外收费;另一个是永久仓库(storage),放一件收一件的钱。能放在临时口袋里处理的,绝不往仓库搬。

函数内部声明的局部变量默认就是这样的“临时口袋”:它们在函数执行期间存在,结束后自动释放,读写只需3 Gas。相比之下,从存储中读一个变量(SLOAD)要花100~2100 Gas,是内存读取的几十到几百倍。于是最经典的优化手段出现了:把循环外要反复读的存储值,先拷进memory里缓存一份,循环内只碰内存。

更进阶的用法还有:函数参数声明为 calldata,表示“只读的货单”,直接引用交易原始数据,比copy到memory再读又省一笔;在确定不会溢出的场景(如只做加法且值有上界)里用 unchecked 块跳过安全校验,也能省下大量算术指令的Gas。

搬家师傅的经验是:能随手做的准备,别等到仓库门口才做。提前把要放的物品放进口袋,到了仓库一次性拿出来摆好,省下的每一步,都是实打实的Gas。

循环内SLOAD次数⇒循环外MLOAD\text{循环内SLOAD次数} \Rightarrow \text{循环外MLOAD}
公式解读:把“每次循环都去仓库搬一趟”改成“进循环前一次性搬进临时口袋”,循环体里的n次SLOAD就被替换成接近免费的MLOAD。
  1. 识别循环中的存储读取循环体内每次引用 storage 变量都是一次SLOAD。
    n×SLOAD,n=循环次数n \times \text{SLOAD},\quad n = \text{循环次数}
  2. 循环外缓存到内存用局部变量(memory)先保存一份,循环内读它。
    SLOAD×1+MLOAD×n,100∼2100≫3\text{SLOAD} \times 1 + \text{MLOAD} \times n,\quad 100\sim2100 \gg 3
  3. 加入unchecked的收益跳过溢出检查后,算术指令成本进一步下降。
    ADDchecked=3,ADDunchecked=3−校验指令开销\text{ADD}_{\text{checked}} = 3,\quad \text{ADD}_{\text{unchecked}} = 3 - \text{校验指令开销}

数据容器选择:选箱子还是货架

搬家公司仓库里有两种存放方式:开放式货架(mapping)和带编号的箱子阵列(array)。货架上的每个位置都有固定标签,找东西按标签一步到位;箱子阵列则要一件件翻找,货越多找得越慢。

mapping是键值对存储,定位一个键只需一次哈希运算(O(1));数组是连续存储,要查找某个下标必须线性遍历(O(n)),而且长度变化会触发扩容或收缩的额外成本。因此在“按用户ID查余额”“按tokenId查所有者”这类场景里,mapping几乎总是更优选择。

但容器选择不止“货架vs箱子”一个维度。有些数据压根不该进仓库:比如只用来记录历史、合约逻辑不需要读取的日志型信息,应该用事件(Event)而非状态变量。事件只写进链下可检索的日志区,不占存储槽,成本只有日志费,比SSTORE便宜一两个数量级。删除用不上的数据也能拿到返还Gas——把不用的箱子退租,账本上省出一笔钱。

师傅挑容器的口诀是:“常用工具摆开放货架,搬完就完事的清单写进运单就行。”选择正确的容器,让每一次存取都花在刀刃上。

mapping查询=O(1),数组遍历=O(n)\text{mapping查询} = O(1), \quad \text{数组遍历} = O(n)
公式解读:容器选型本质是用“存储布局”换“查询速度”:货架一步定位,箱子逐个翻找。需要遍历的场景才选数组,其余时候mapping带路更快更省。
  1. mapping定位开销无论键多少,一次keccak256哈希计算即可定位。
    mapping[k]=keccak256(k)⇒O(1)\text{mapping}[k] = \text{keccak256}(k) \Rightarrow O(1)
  2. 数组遍历开销数组元素线性排列,最坏情况要读完整个数组。
    array[i]⇒O(n),扩容时还需复制旧元素\text{array}[i] \Rightarrow O(n),\quad \text{扩容时还需复制旧元素}
  3. Event vs SSTORE日志记录不进入存储树,成本远低于一次存储写入。
    GasEvent≪GasSSTORE\text{Gas}_{\text{Event}} \ll \text{Gas}_{\text{SSTORE}}

实战优化工具:用尺子量着搬

搬家公司不会凭感觉收钱,而是用电子秤、卷尺和报价软件算出每件物品的费用。合约开发同样离不开一整套“测量工具”——Gas排查不能靠拍脑袋,要用工具把消耗摸得清清楚楚。

开发中最常见的三件套:Remix IDE内置的Gas估算器,可以单步执行并看到每个操作码的Gas消耗;Hardhat生态的hardhat-gas-reporter插件,会在跑测试时自动输出每个函数的平均Gas报告;Foundry的forge test --gas-report则生成结构化对比表,适合测试驱动下的优化迭代。

优化迭代的标准流程是:先跑一遍测试拿到基线Gas报告,定位消耗最高的函数,针对存储读写、循环、变量布局逐一优化,再重跑同一套测试,用节省率量化成果,确认没有引入回归。

师傅收工前会复称一遍所有箱子,确认报价无误。合约上线前同样必须用工具复核每一笔关键交易的Gas——优化不是感觉,而是用数据说话的工程素养。

Gas节省率=优化前Gas−优化后Gas优化前Gas×100%\text{Gas节省率} = \frac{\text{优化前Gas} - \text{优化后Gas}}{\text{优化前Gas}} \times 100\%
公式解读:优化不是玄学,而是可量化、可对比的工程指标。每次改动都能算出一笔明确的“省钱账”。
  1. 基线测量优化前先记录每个函数的关键路径Gas。
    Gasbefore=各函数Gas消耗的基线值\text{Gas}_{\text{before}} = \text{各函数Gas消耗的基线值}
  2. 优化后复测应用技巧后跑同一测试用例,得到新Gas值。
    Gasafter=优化后的消耗\text{Gas}_{\text{after}} = \text{优化后的消耗}
  3. 计算节省率两者之差占优化前的百分比。
    Gasbefore−GasafterGasbefore×100%\frac{\text{Gas}_{\text{before}} - \text{Gas}_{\text{after}}}{\text{Gas}_{\text{before}}} \times 100\%

AI 应用场景

DeFi去中心化交易所的Swap合约调优

阶段:智能合约开发与部署迭代

解决的问题:用户每做一次代币兑换(Swap)都要付一次Gas。Gas越高,小额交易越无利可图,用户会大量流失到Layer2。传统方案只关注功能正确,上线后才发现交易费用高得无人问津。

具体案例:某DeFi协议参考Uniswap V2的做法,在Swap核心函数中把多次存储状态更新合并到最后一次写入,中间结果全部缓存在memory里;同时将交易对合约的工厂部署采用单例模式,把可复用的storage布局固化,使单次Swap的Gas稳定在行业均值附近。

NFT批量铸造的Gas瘦身

阶段:合约上线前的Gas调优

解决的问题:批量铸造NFT时,如果每个token都触发一次 ownerOf 映射的SSTORE,Gas会随数量线性爆炸。高Gas让“批量Mint”名存实亡,也抬高了小散户的参与门槛。

具体案例:一个链上艺术平台把元数据(图片URL、描述)放进事件里广播,链上只存储“已铸造数量”计数器;在 mintBatch 中通过单次存储写入配合位运算记录连续ID段,将单件铸造的边际Gas降低了40%以上。

Python 演示

python51 行
class StorageLayout:    SSTORE_NEW_SLOT = 20000     def __init__(self, name):        self.name = name        self.slots = []        self._var_count = 0     def add_variable(self, bit_width):        if not self.slots:            self.slots.append(0)        if self.slots[-1] + bit_width > 256:            self.slots.append(0)        self.slots[-1] += bit_width        self._var_count += 1     def packed_gas(self):        return self.SSTORE_NEW_SLOT * len(self.slots)     def separate_gas(self):        return self.SSTORE_NEW_SLOT * self._var_count  def make_layout(widths):    layout = StorageLayout("demo")    for w in widths:        layout.add_variable(w)    return layout  def report(widths):    packed = make_layout(widths)    sg = packed.separate_gas()    pg = packed.packed_gas()    save = sg - pg    print(f"变量位宽列表: {widths}")    print(f"  打包后槽位: {len(packed.slots)} 个, Gas: {pg:,}")    print(f"  独立占位槽: {packed._var_count} 个, Gas: {sg:,}")    print(f"  节省: {save:,} Gas ({save / sg * 100:.1f}%)\n")  def main():    print("=== 存储打包 Gas 模拟 ===\n")    report([64, 64, 64, 64])    report([128, 256, 128])    report([8, 8, 16, 8])    report([8, 256, 8])  if __name__ == "__main__":    main()
目的:用Python模拟Solidity存储布局,对比“打包”与“独立占位”的SSTORE成本,直观量化状态变量打包的Gas收益。
  1. class StorageLayout模拟一个存储布局容器,slots 列表表示最终占用的存储槽。
  2. add_variable(bit_width)把变量按位宽放进当前槽,放不下就开新槽,实现“顺序打包”。
  3. packed_gas()打包方案的成本 = 槽位数 × 每次SSTORE的2万Gas。
  4. separate_gas()不做打包时的成本 = 变量个数 × 2万Gas。
  5. report([64, 64, 64, 64])4个uint64打包进1个槽,成本从8万降到2万,节省75%。
  6. report([128, 256, 128])中间插入uint256,本可以2个槽放下的数据变成3个槽,浪费立刻显现。

常见误区

误区初学者常以为类型越小越省Gas,于是把合约里能改成uint8的全部改成uint8,结果Gas报告反而没有明显下降。

正解Gas省钱的关键不是单个变量的大小,而是“存储槽的利用率”。Solidity按32字节的槽来收费,一个uint8独自占一个槽时,跟uint256占一个槽交的SSTORE费用完全一样。小类型只有在“能和其他小类型共享一个槽”时才真正省钱。把类型改小却不调整声明顺序,等于换了个更小的箱子却仍然独占一格货架。

为何常见:因为“越小越省”符合日常直觉,而存储槽的计费模式藏在EVM底层,不亲手查Gas报告很难意识到。

误区很多人用“行数”衡量Gas消耗,觉得删几行代码就能降Gas,甚至通过把多个表达式挤在一行来“优化”。

正解Gas消耗由实际执行的操作码决定,与源码行数没有直接关系。压缩代码可能会触发完全相同的操作码序列,账单一分不减;而某些“变长”的技巧——比如循环展开(把循环体复制几份)——反而能减少循环控制指令,省下一笔笔小额Gas。正确的测量方式是跑Gas报告,而不是用眼睛数行数。

为何常见:多数人的性能直觉来自普通软件开发中的“代码越短执行越快”,但EVM按操作码计费的模型里,行数是无效指标。

误区学会 `unchecked` 省Gas后,开发者在所有算术运算上都套上它,觉得溢出检查是多余的。

正解unchecked 省掉的Gas正是Solidity自动插入的安全校验。一旦运算真实发生溢出,结果会静默回绕,轻则逻辑错误,重则资金被盗——历史上多起著名的攻击事件都源于整数溢出。正确用法是只在“能由外部约束证明不会溢出”的场景使用,比如时间戳差值、有明确上限的计数器。

为何常见:因为溢出在测试环境里难复现,而Gas节省是立竿见影的,安全收益被短期利益掩盖了。

误区事件这么便宜,把游戏战绩、订单状态全部写成Event,链上查不到了再去事件日志里找。

正解事件日志是“链下检索”的数据,不能直接被合约逻辑读取。如果合约需要判断某个状态、根据历史做计算(例如余额校验),就必须用存储变量。把必须可读的状态放进事件,等于把货物运到了无法取回的仓库暗格——数据在,但合约够不着。事件适合存“给外部看的公告”,存储适合存“合约自己要用的账本”。

为何常见:因为事件Gas确实比SSTORE低很多,初学者容易被成本差吸引,忽略了“谁在读数据”这个根本问题。

面试题

Q1. 以太坊智能合约执行时,以下哪类操作消耗Gas最高?

单选题 · easy

  • A.内存读写MLOAD
  • B.存储写入SSTORE← 正确答案
  • C.算术运算ADD
  • D.栈操作PUSH

答案:B。存储写入涉及全网节点的账本同步,SSTORE可达数万Gas;而内存读写、算术运算都只要个位数Gas。可以把存储想象成“永久仓库租金”,其他操作只是“临时工工时费”。

Q2. EIP-1559中“基础费”被销毁而非给矿工,主要目的是什么?

单选题 · easy

  • A.增加矿工直接收益
  • B.让Gas价格可预测并减少通胀← 正确答案
  • C.防止重入攻击发生
  • D.直接提高区块容量上限

答案:B。基础费按区块拥堵度动态调整并销毁,让用户对下一块费用有更稳定的预期,同时ETH总量趋向通缩;矿工只拿优先费。这与安全性或区块容量无关,是经济模型设计。

Q3. 以下哪种状态变量排布能放入同一个存储槽?

单选题 · mid

  • A.四个uint8连续声明← 正确答案
  • B.bool加上uint256变量
  • C.uint256加上两个uint8
  • D.两个uint128加uint8

答案:A。一个存储槽256位。四个uint8共32位可共存一槽;B、C、D的总位宽都超过256(分别约328位、272位、273位),必然分槽。打包就像往32字节的格层里叠小箱子,装不下就得另开一格。

Q4. 在EVM中,以下哪个操作会触发Gas返还(refund)?

单选题 · mid

  • A.SLOAD读取热存储槽
  • B.SSTORE将非零值改为零← 正确答案
  • C.MLOAD读取内存区域
  • D.CALL调用外部合同

答案:B。把存储槽从非零改为零相当于“退租仓库”,协议对释放存储空间的行为给予约4800 Gas的返还;读取操作、调用操作和内存读写都没有“释放空间”的语义,不会返还。

Q5. 以下哪些做法能有效降低合约Gas消耗?

多选题 · mid

  • A.用mapping替代频繁遍历的数组← 正确答案
  • B.在循环里重复读取存储槽
  • C.将结构体成员紧凑排序← 正确答案
  • D.把历史数据写入事件← 正确答案

答案:ACD。mapping查找O(1)天然比遍历数组省;结构体紧凑排序能提高槽位利用率;事件不占存储槽、成本远低于SSTORE。B恰恰相反,循环内重复SLOAD是典型的Gas浪费,应先把存储值缓存到memory。

Q6. 状态变量声明顺序影响Gas的关键原因是什么?

单选题 · mid

  • A.Solidity编译器自动打包连续小类型← 正确答案
  • B.变量名的字符长度设置
  • C.编译器优化开关状态
  • D.合约注释数量的多少

答案:A。编译器会把连续声明且总大小不超过256位的小类型变量打包进同一存储槽。声明顺序决定了哪些变量能“住进同一格”,从而减少槽位总数和后续SSTORE/SLOAD成本。

Q7. 下列关于SSTORE成本的说法正确的有哪些?

多选题 · hard

  • A.首次写入新空槽约消耗20000 Gas← 正确答案
  • B.修改已有槽比新增槽便宜← 正确答案
  • C.清空存储槽可以获得Gas返还← 正确答案
  • D.存储读取永远完全免费

答案:ABC。0→非零是“新租仓库”最贵;非零→非零是“换货”略便宜;非零→零是“退租”有返费。D错误:SLOAD按冷热状态收100~2100 Gas,绝非免费。

Q8. 使用 unchecked 块省Gas的原理是什么?

单选题 · easy

  • A.跳过安全的算术校验← 正确答案
  • B.完全关闭存储功能
  • C.开启并行多线程处理
  • D.直接压缩运行代码体积

答案:A。Solidity默认在算术指令后插入溢出检查,unchecked 跳过这些检查从而减少执行的操作码。代价是溢出不再报错、结果可能回绕,必须在能证明不会溢出的场合使用。

Q9. 循环中需要遍历一个storage数组时,最省Gas的做法是?

单选题 · hard

  • A.循环内每次读写存储槽
  • B.拷贝到内存后再遍历← 正确答案
  • C.循环中用哈希重新定位
  • D.用多个局部变量暂存

答案:B。数组在storage中,循环内按下标访问,每次都是一次SLOAD;先把整个数组拷贝到memory,循环内只做3 Gas的MLOAD,可省下n×(SLOAD−MLOAD)的巨大开销。哈希定位不会减少读取,多个局部变量也无法覆盖任意长度的遍历。

Q10. Gas优化中的“平衡之道”主要包括哪些维度?

多选题 · hard

  • A.优化与代码可读性平衡← 正确答案
  • B.优化与合约安全性平衡← 正确答案
  • C.用最少Gas保留所需功能← 正确答案
  • D.合约内绝不使用任何存储

答案:ABC。过度优化会让代码晦涩难懂、难以审计;unchecked 等技巧还会放大安全风险,所以最优解法是在保证功能与安全的前提下尽量省Gas。D是极端化误区——完全不使用存储,等于放弃合约的核心状态能力。