CKB/CKB-VM 宏指令融合 MOP

原理介绍

宏指令融合(Macro-Operation Fusion, MOP Fusion)是现代微处理器体系结构中的一种性能优化技术: 在指令解码阶段将若干相邻的宏操作序列合并为单条内部微操作, 从而降低指令分派开销并减少执行周期. 该技术已在 x86, ARM 等主流架构的高性能微架构中得到广泛应用, 也可在虚拟机层面通过解码器实现.

CKB-VM 在 2021 Edition 硬分叉中引入了宏指令融合机制, 并在 2023 Edition 中进一步扩充了融合规则集. 本文介绍 CKB-VM 当前支持的全部融合模式.

以有符号除法为例: 计算 100 / 42 并分别获取商与余数, 对应的 RISC-V 汇编如下:

addi t0, t0, 100
addi t1, t1, 42

div a0, t0, t1
rem a1, t0, t1

在非融合模式下, CKB-VM 会依次执行 divrem 两条独立指令. 然而, CKB-VM 的 ASM 解释后端运行于 x86-64 宿主机之上, x86-64 的 IDIV 指令在执行有符号除法时会同时产生商与余数(参考手册). 因此, 当 divrem 相邻出现且两个操作数完全相同时, 可以将这两条 RISC-V 指令融合为单条内部伪指令, 实际执行时仅执行一条 IDIV, 从而大幅度降低执行周期.

CKB-VM 在解码阶段检测相邻指令序列是否满足融合条件, 若满足则将其合并为一条内部伪指令; 这些伪指令在 x86-64 后端通常仅对应一条原生指令, 而非融合前的多条指令.

支持的宏融合指令

CKB-VM 支持的宏融合规则随版本迭代逐步扩充, 目前共有 15 条融合规则. 融合后指令的执行周期数等于原始序列中最高单条指令周期数, 其余指令周期归零. 下面分别介绍两次硬分叉引入的融合规则.

CKB 2021 Edition

ADC: 带进位加法(Add with Carry), 5 条指令, 周期 1.

用于 128 位整数加法的低位字计算: 将两个 64 位值相加并传播进位.

add  r0, r0, r1
sltu r1, r0, r1
add  r0, r0, r2
sltu r2, r0, r2
or   r1, r1, r2

激活条件:

  • r0 != x0, r1 != x0, r2 != x0

SBB: 带借位减法(Subtract with Borrow), 5 条指令, 周期 1.

用于 128 位整数减法的低位字计算: 将两个 64 位值相减并传播借位.

sub  r0, r1, r0
sltu r2, r1, r4
sub  r1, r0, r3
sltu r3, r0, r1
or   r0, r3, r2

激活条件:

  • r0 != x0, r1 != x0, r2 != x0, r3 != x0
  • 第二条 sltu 的 rs2(r4)不受融合规则约束

WIDE_MUL: 有符号宽乘法, 2 条指令, 周期 5.

同时计算两个有符号 64 位整数之积的高 64 位与低 64 位, 得到完整的 128 位结果.

mulh r0, r1, r2
mul  r3, r1, r2

激活条件:

  • r0 != r1, r0 != r2, r0 != r3

WIDE_MULU: 无符号宽乘法, 2 条指令, 周期 5.

mulhu r0, r1, r2
mul   r3, r1, r2

激活条件:

  • r0 != r1, r0 != r2, r0 != r3

WIDE_MULSU: 有符号与无符号宽乘法, 2 条指令, 周期 5.

mulhsu r0, r1, r2
mul    r3, r1, r2

激活条件:

  • r0 != r1, r0 != r2, r0 != r3

WIDE_DIV: 有符号宽除法, 2 条指令, 周期 32.

同时计算有符号除法的商与余数, 对应 x86-64 的单条 IDIV 指令.

div r0, r1, r2
rem r3, r1, r2

激活条件:

  • r0 != r1, r0 != r2, r0 != r3

WIDE_DIVU: 无符号宽除法, 2 条指令, 周期 32.

divu r0, r1, r2
remu r3, r1, r2

激活条件:

  • r0 != r1, r0 != r2, r0 != r3

FAR_JUMP_REL: PC 相对远跳转, 2 条指令, 周期 3.

用于调用偏移量超出 jal 可达范围(±1 MiB)的函数.

auipc ra, hi
jalr  ra, ra, lo

激活条件:

  • jalrrdrs1 均为 ra

FAR_JUMP_ABS: 绝对地址远跳转, 2 条指令, 周期 3.

lui  ra, hi
jalr ra, ra, lo

激活条件:

  • jalrrdrs1 均为 ra

LD_SIGN_EXTENDED_32_CONSTANT: 加载 32 位符号扩展立即数, 2 条指令, 周期 1.

将一个 32 位符号扩展立即数装入寄存器.

lui   r0, hi
addiw r0, r0, lo

激活条件:

  • luiaddiw 的目标寄存器相同

CKB 2023 Edition

ADCS: 带进位输出的加法(Overflowing Addition), 2 条指令, 周期 1.

ADC 的简化形式, 仅执行一次加法并同时输出进位标志, 也是 ADD3A/ADD3B/ADD3C 的基本组成单元.

add  r0, r1, r2
sltu r3, r0, r1
// 或
add  r0, r2, r1
sltu r3, r0, r1

激活条件:

  • r0 != r1, r0 != x0

SBBS: 带借位输出的减法(Borrowing Subtraction), 2 条指令, 周期 1.

SBB 的简化形式, 仅执行一次减法并同时输出借位标志.

sub  r0, r1, r2
sltu r3, r1, r2

激活条件:

  • r0 != r1, r0 != r2

ADD3A: 进位传播加法变体 A, 3 条指令, 周期 1.

加法结果写回 rs2 所在寄存器, 进位标志存入独立寄存器, 再加上额外的进位输入.

add  r0, r1, r0
sltu r2, r0, r1
add  r3, r2, r4

激活条件:

  • r0 != r1, r0 != r4, r2 != r4, r0 != x0, r2 != x0

ADD3B: 进位传播加法变体 B, 3 条指令, 周期 1.

进位标志回写至 rs1 所在寄存器, 再与第三个操作数相加.

add  r0, r1, r2
sltu r1, r0, r1
add  r3, r1, r4

激活条件:

  • r0 != r1, r0 != r4, r1 != r4, r0 != x0, r1 != x0

ADD3C: 进位传播加法变体 C, 3 条指令, 周期 1.

进位标志存入独立寄存器后原地累加第三个操作数.

add  r0, r1, r2
sltu r3, r0, r1
add  r3, r3, r4

激活条件:

  • r0 != r1, r0 != r4, r3 != r4, r0 != x0, r3 != x0

LD_SIGN_EXTENDED_32_CONSTANT (auipc 变体): PC 相对地址物化, 2 条指令, 周期 1.

CKB 2023 Edition 将 auipc + addi 纳入与 lui + addiw 相同的融合操作码(LD_SIGN_EXTENDED_32_CONSTANT), 用于将 PC 相对偏移地址在解码阶段直接物化为立即数常量.

auipc r0, hi
addi  r0, r0, lo

激活条件:

  • auipcaddi 的目标寄存器相同

性能分析

以下基准测试对 CKB-VM ASM 后端在启用/关闭 MOP 融合时的表现进行比较:

  • asm: ASM 后端, 不启用 MOP 融合
  • mop: ASM 后端, 已启用 MOP 融合

测试对象均为 CKB 链上常见的密码学算法, 使用 Criterion 测量 100 次采样的中位周期数. 下表中的 cycles 指的是 host 端 x86-64 CPU 上执行 CKB-VM 时的周期数, 而不是 RISC-V 指令周期数, 这点值得注意.

周期数对比

算法 asm (cycles) mop (cycles) 变化幅度
ed25519 9,508,012 9,261,983 −2.6%
k256_ecdsa 37,225,290 41,084,856 +10.4%
k256_schnorr 17,603,180 18,830,487 +7.0%
p256 28,359,207 24,815,077 −12.5%
rsa 27,871,846 27,321,671 −2.0%
secp256k1_ecdsa 10,776,242 10,732,561 −0.4%
secp256k1_schnorr 10,465,140 10,622,287 +1.5%
sphincsplus_ref 279,791,883 232,608,091 −16.9%

分析

显著受益: sphincsplus_ref (−16.9%) 和 p256 (−12.5%) 是 MOP 融合的最大受益者. SPHINCS+ 以哈希函数调用为主, 含有大量 256 位整数加减链, ADD3A/B/C 和 ADC/ADCS 系列可在关键循环中频繁触发. P-256 的域乘法同样有较多宽乘法序列, WIDE_MUL 融合在此发挥效果.

收益有限: secp256k1 和 RSA 的提升不足 2%. secp256k1 在 CKB 生态中经过高度手工优化, 编译器生成的指令序列未必与 MOP 融合窗口对齐; RSA 的主要开销集中在模幂的乘法累加, 部分路径可被 WIDE_MUL 覆盖但绝对量较小.

出现回退: k256 系列 (k256_ecdsa +10.4%, k256_schnorr +7.0%) 和 secp256k1_schnorr (+1.5%) 在开启 MOP 后实际变慢. k256 回退尤为明显, 且其置信区间也明显比 asm 组宽 (asm/k256_ecdsa 区间约 150K cycles, mop/k256_ecdsa 约 2000K cycles), 说明 MOP 解码在该代码路径上引入了额外的分支预测压力.

值得注意的是, 以上测试均使用了 Rust 和 C 语言编写的算法实现, 其编译器优化策略和生成的指令序列可能不完全适配 MOP 融合规则, 因此实际性能表现可能受限于特定实现细节. 实际上, 针对热点代码路径进行手工汇编优化可以更好地利用 MOP 融合, 实际性能提升可能远超上述基准测试结果.

您可以在 此仓库 中找到完整的基准测试代码和数据.