Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

算术运算与位操作优化

在以太坊虚拟机(EVM)中,不同操作码(Opcodes)在计算与执行资源上的消耗存在显著差异。

乘除、条件分支和求幂的执行成本由生成的字节码决定。数学与位操作优化需要同时保持数值语义,并在固定编译配置下比较实际 Gas。


EVM 算术操作码 Gas 消耗参考表

根据 EVM Codes 现行计费规范,常用算术与位操作的单次执行基础成本如下:

操作码类别具体操作码执行 Gas 开销语义与物理操作
位级移位 (Shift)SHL, SHR, SAR3 Gas二进制左移、逻辑右移、算术右移
基础逻辑 (Logic)AND, OR, XOR, NOT3 Gas按位与、或、异或、取反
加减运算 (Add/Sub)ADD, SUB3 Gas256 位模加法与模减法
高阶乘除 (Mul/Div)MUL, DIV, SDIV, MOD5 Gas256 位高精度乘除与求模
指数求幂 (Exp)EXP10 Gas + 50 Gas/字节动态阶梯计费(指数所占字节越多越昂贵)

2 的幂次方:位移替代乘除法

对于涉及 2 的整数幂(\(2, 4, 8, 16, \dots\))的无符号整数运算,移位可能降低指令成本。先确认语义,再在相同编译器、优化器与 EVM 目标下测量;优化器可能已完成替换:

1. 乘法向左移位 (SHL)

// 原生乘法:消耗 MUL (5 Gas)
uint256 y = x * 8;

// 只有确认 x <= type(uint256).max / 8 时,才与上面的受检乘法等价
uint256 y = x << 3;

2. 除法向右移位 (SHR)

// 原生除法:消耗 DIV (5 Gas)
uint256 y = x / 4;

// 对 uint256,右移 2 位与除以 4 的取整结果一致
uint256 y = x >> 2;

注意与边界:Solidity 0.8+ 的乘法默认检查溢出,左移则截断高位而不检查溢出;不能无条件把 x * 8 换成 x << 3。有符号负数的右移向负无穷取整,除法向零取整,也不能直接替换。

指令成本与完整成本:位移操作仅适用于非负无符号整数(uint256),且不可将任意非 2 次幂拆分(例如 \(x \times 3\) 拆分为 \(x + (x \ll 1)\) 会引入额外的 ADD,总开销 \(3 + 3 = 6 \text{ Gas}\),反而高于直接 MUL 的 5 Gas)。


常量折叠与编译期预计算 (Constant Folding)

常量表达式可以在编译期求值。下面两种秒数写法都会折叠为相同常量;是否收费还取决于调用、返回与完整字节码,不能称为“0 运行时 Gas”:

// 两种写法都能在编译期折叠,不会每次执行乘法
uint256 public constant SECONDS_PER_DAY = 24 * 60 * 60;
uint256 public constant SECONDS_IN_DAY = 86400;

// 内建属性直接表达类型上限
uint256 public constant MAX_UINT = type(uint256).max;

比较操作与除法防溢出

在涉及乘除混合的大小比较判断时,应通过代数等价变形消除潜在的算术溢出与 Revert 异常:

// ❌ 存在中间乘法溢出导致业务意外 Revert 的风险
if (a * b > c) {
    // 若 a * b 超过 2^256 - 1,Solidity 0.8 会直接报错中断
}

// ✅ 代数等价变换:先除后比较,消除溢出隐患(需保证 b > 0)
if (a > c / b) {
    // 逻辑安全且避免了乘法异常中断
}

多属性位压缩与掩码提取 (Bit Packing & Bitmasks)

在追求极致存储密度的场景下,利用位运算将多个布尔标记或离散枚举压缩进同一个 256 位字中,是大幅减少 SSTORE 槽位占用的关键武器:

// 单一 uint256 管理多个状态标志的简化示例,与 USDC 实现不同
uint256 private _stateFlags;

uint256 private constant FLAG_PAUSED = 1 << 0;     // 第 0 位:暂停标记
uint256 private constant FLAG_BLACKLIST = 1 << 1;  // 第 1 位:黑名单标记
uint256 private constant FLAG_INITIALIZED = 1 << 2;// 第 2 位:初始化标记

// 设置标记 (Set Flag)
function setPaused(bool paused) internal {
    if (paused) {
        _stateFlags |= FLAG_PAUSED;
    } else {
        _stateFlags &= ~FLAG_PAUSED;
    }
}

// 查询标记 (Check Flag)
function isPaused() internal view returns (bool) {
    return (_stateFlags & FLAG_PAUSED) != 0;
}

参考文献与规范