1. 课程导入

各位同学,咱们今天正式开讲 RISC-V 向量扩展。说实话,我第一次接触 RVV 的时候,脑子里全是 x86 AVX 和 ARM NEON 的影子。但真正上手之后才发现——RVV 的设计哲学跟它们完全不是一回事。它不是简单地把 SIMD 宽度写死在硬件里,而是给了你一套可以动态伸缩的向量抽象层。

这一章,我们先打好地基。把寄存器、LMUL、VL、AVL 这几个核心概念彻底吃透。后面做性能调优的时候,你才会知道瓶颈到底在哪。

1.1 向量寄存器与 LMUL

RVV 的向量寄存器组,我习惯叫它 VRF(Vector Register File)。它包含 32 个寄存器,名字从 v0 到 v31。每个寄存器的位宽不是固定的——这是 RVV 跟传统 SIMD 最大的区别。

核心要点: 向量寄存器的物理位宽由硬件实现决定,但逻辑位宽由 LMUL(向量长度乘数)和 VLEN(向量寄存器长度)共同控制。

咱们先看一个具体的例子。假设硬件 VLEN = 128 位,那么每个向量寄存器就是 128 位宽。但如果你把 LMUL 设为 2,两个寄存器 v0 和 v1 就会拼接成一个 256 位的逻辑寄存器。LMUL 可以取 1、2、4、8,也可以取分数 1/2、1/4、1/8。

LMUL 值 逻辑寄存器宽度(VLEN=128) 占用物理寄存器数 典型应用场景
1/8 16 位 1/8 个(共享) 窄元素类型、掩码操作
1/4 32 位 1/4 个(共享) 小批量数据处理
1/2 64 位 1/2 个(共享) 短向量运算
1 128 位 1 个 默认配置,通用计算
2 256 位 2 个 数据密集型、矩阵乘法
4 512 位 4 个 高吞吐计算、图像处理
8 1024 位 8 个 极致并行、科学计算

我的经验: 在项目中,我一般先用 LMUL=1 做原型验证。等算法逻辑跑通之后,再逐步增大 LMUL 来压榨性能。但要注意——LMUL 越大,寄存器压力也越大。我曾经在一个卷积核里用了 LMUL=8,结果编译器疯狂 spill,性能反而倒退了 30%。

为什么要有 LMUL 这个设计?说白了,就是为了让同一套指令集能适配不同宽度的硬件。你可以在 128 位的低功耗芯片上跑 RVV,也可以在 1024 位的高性能处理器上跑。代码不用改,只需要调整 LMUL 和 VLEN 的配置。

1.2 向量长度 VL 与 AVL

这两个概念,我刚开始学的时候经常搞混。咱们用一个场景来理解。

假设你要处理 1000 个 float 数据。硬件 VLEN=128 位,每个 float 占 32 位。那么一个向量寄存器一次能装 4 个 float。但 1000 不是 4 的倍数,怎么办?

RVV 引入了两个关键概念:

  • AVL(Application Vector Length):应用程序希望处理的元素总数。在这个例子里,AVL = 1000。
  • VL(Vector Length):当前向量指令实际处理的元素个数。它由硬件根据 AVL、VLEN、元素大小和 LMUL 自动计算。

每次向量操作之后,硬件会更新 VL 的值。你不需要手动计算剩余元素——RVV 的 vsetvli 指令会帮你搞定。

// 伪代码示例:处理 1000 个 float
int avl = 1000;
while (avl > 0) {
    // vsetvli 会设置 VL = min(avl, 硬件能处理的最大元素数)
    size_t vl = vsetvli_e32m1(avl);
    
    // 加载 vl 个元素
    vfloat32m1_t vec = vle32_v_f32m1(ptr, vl);
    
    // 执行向量运算
    vec = vfadd_vv_f32m1(vec, vec, vl);
    
    // 存储结果
    vse32_v_f32m1(ptr, vec, vl);
    
    // 更新指针和剩余元素数
    ptr += vl;
    avl -= vl;
}

注意: 千万不要手动计算 VL 然后硬编码到指令里。我见过有人这么干,结果换了一个 LMUL 配置,代码就全崩了。VL 必须由 vsetvli 动态设置,这是 RVV 的黄金法则。

为什么会这样设计?你想想看,如果硬件 VLEN 是 256 位,LMUL=2,那一个逻辑寄存器能装 16 个 float。但如果你把代码移植到 VLEN=128 的芯片上,同样的 LMUL 只能装 8 个。如果 VL 是硬编码的,代码就废了。RVV 通过动态 VL 机制,让你的代码天然具备可移植性。

1.3 知识体系总览

下面这张图,我把本章的核心概念和它们之间的关系画了出来。建议你保存下来,后面学指令集的时候随时回来对照。

RVV 向量扩展核心概念关系图 RVV 指令集架构 向量寄存器 & LMUL 向量长度 VL 应用向量长度 AVL VLEN(硬件宽度) LMUL 乘数因子 vsetvli 指令 动态计算 VL 循环剥离 实践要点: LMUL 越大吞吐越高 → 寄存器压力越大 → 需要平衡

1.4 关键设计哲学

学 RVV 的时候,我建议你时刻记住三个设计原则:

  1. 可伸缩性:同一份代码,从低功耗嵌入式到高性能计算,都能跑。LMUL 和 VL 就是实现这个目标的工具。
  2. 动态性:VL 不是编译时确定的,而是运行时由硬件计算。这意味着你的代码天然适应不同宽度的硬件。
  3. 组合性:LMUL、VLEN、元素大小、掩码——这些参数可以自由组合。但组合越多,调优的坑也越多。

一句话总结: RVV 的向量化不是「写死宽度」,而是「描述计算模式,让硬件决定每次处理多少数据」。