算术运算与位操作优化
在以太坊虚拟机(EVM)中,不同操作码(Opcodes)在计算与执行资源上的消耗存在显著差异。
乘除、条件分支和求幂的执行成本由生成的字节码决定。数学与位操作优化需要同时保持数值语义,并在固定编译配置下比较实际 Gas。
EVM 算术操作码 Gas 消耗参考表
根据 EVM Codes 现行计费规范,常用算术与位操作的单次执行基础成本如下:
| 操作码类别 | 具体操作码 | 执行 Gas 开销 | 语义与物理操作 |
|---|---|---|---|
| 位级移位 (Shift) | SHL, SHR, SAR | 3 Gas | 二进制左移、逻辑右移、算术右移 |
| 基础逻辑 (Logic) | AND, OR, XOR, NOT | 3 Gas | 按位与、或、异或、取反 |
| 加减运算 (Add/Sub) | ADD, SUB | 3 Gas | 256 位模加法与模减法 |
| 高阶乘除 (Mul/Div) | MUL, DIV, SDIV, MOD | 5 Gas | 256 位高精度乘除与求模 |
| 指数求幂 (Exp) | EXP | 10 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;
}