如果说 RISC-V 这几年最值得关注的变化,我的判断是:它已经完成了“从无到有”,现在正在经历“从能跑到跑得快”的关键阶段。而“跑得快”这三个字,第一个绕不开的衡量标准,就是向量计算性能,也就是 RVV Benchmark。最近看到一份资料,把 SiFive P870、Lanxin LX500、Epic Semi Contrail AIx 这三款芯片放在同一份 RVV 基准测试框架下对比。很多人看到这种材料的第一反应是找排名:谁分数高,谁就强。但我的建议是别急着下结论——这类对比真正值得读的,不是跑分数字本身,而是三个芯片在这份 benchmark 里暴露出来的设计取舍,以及这些取舍对你后续选型和软件移植意味着什么。
这篇文章不打算抛出一串“看起来很厉害”的跑分表。因为跑分表的可信度,完全取决于测试条件、工具链版本、向量长度配置和功耗策略,脱离这些谈分数没有意义。本文要做的是三件事:第一,讲清楚 RVV Benchmark 到底在测什么,为什么它比传统 CPU benchmark 更能反映 RISC-V 处理器的真实水平;第二,拆解 SiFive P870、Lanxin LX500、Epic Semi Contrail AIx 这三款芯片放在一起对比的合理性,以及每类芯片在向量计算路线上的典型取舍;第三,提供一套从环境搭建、代码实现到结果解读的实操方法,帮你建立自己的 RVV 性能评估能力。如果你正在做 RISC-V 芯片选型、算法移植,或者需要评估某款处理器的 AI 推理与信号处理能力,这篇文章应该能帮你少走不少弯路。
1. 为什么现在该认真看 RVV Benchmark
RISC-V 通用计算能力过去几年已经追上了不少嵌入式场景的需求,但向量计算一直是个尴尬地带。早期 RISC-V 处理器大多只支持标量指令集,做 AI 推理要么靠 NPU 硬件模块,要么靠编译器把循环拆开一层层跑,效率不高。RVV(RISC-V Vector Extension)的出现,本来就是为了补齐这个短板。但真正让 RVV 从“纸面规范”变成“可评估的技术指标”,其实是最近两三年的事:RVV 1.0 规范稳定、主流编译器开始默认支持、芯片厂商陆续流片量产,这三件事同时发生,RVV Benchmark 才真正有了参考价值。
现在看 RVV Benchmark,背后其实是三个产业层面的变化:
第一,RISC-V 处理器开始从“追求能跑”转向“追求跑得好看”。通用核心的标量性能可以通过提高主频、加大缓存、优化分支预测来实现,这属于经典 CPU 设计路线。但向量性能不一样,它涉及寄存器文件宽度、数据通路设计、内存带宽、编译器代码生成质量等多个环节,任何一个环节掉链子,最终 benchmark 数字都会非常难看。所以 RVV Benchmark 更像是一张“体检单”,暴露的是芯片设计的整体成熟度。
第二,RVV 1.0 和早期草案之间的兼容性矛盾已经摆上台面。很多 2022 年之前设计的芯片基于 RVV 0.7.1 草案版本,指令编码和 1.0 不兼容。这意味着同一段向量化代码,在两颗不同“版本基因”的芯片上,编译器和二进制都可能完全不同。对于开发者和芯片选型人来说,这个分裂状态远比“谁跑分高”更值得关注——它直接决定你的软件栈能不能平滑迁移。
第三,向量性能正在成为 AI 推理和信号处理场景的关键指标。无论是端侧 AI、边缘计算,还是基站信号处理,工作负载大多可以抽象成矩阵乘、卷积、FFT 这类向量密集运算。RVV 指令集在这些场景里的执行效率,几乎决定了处理器在真实应用中的体验。与其听厂商宣传“支持 AI”“支持向量扩展”,不如用一套统一的 RVV Benchmark 看实际吞吐能力。
所以我的核心判断很简单:RVV Benchmark 已经从“跑分娱乐”变成了“软件迁移可行性的验证工具”。你现在看到的任何一份 RVV 对比材料,都值得用下面的方法论去审读一遍,而不是直接跳到结论页看名次。
2. 三款芯片的定位差异与同台对比的逻辑
把 SiFive P870、Lanxin LX500、Epic Semi Contrail AIx 放到同一份 RVV Benchmark 里,表面看是三个产品在比“谁的向量单元更强”,但这个对比本身包含了一个容易被忽略的背景:三款产品在设计定位上并不完全相同。
2.1 SiFive P870:通用高性能核的向量方案
SiFive P870 属于 SiFive Performance 系列,定位是高性能应用处理器核,面向 Linux 类复杂系统、边缘计算、数据中心等场景。从公开信息看,P870 是 SiFive 在 2023 年前后推出的高性能核心,支持 RVV 1.0,设计上比较重视通用计算与向量计算的平衡。它不是一个独立的芯片,而是一个可授权的 CPU IP。这意味着你拿到的 benchmark 结果,很大程度上取决于芯片集成方怎么配置缓存、内存控制器和主频。
P870 放在 RVV Benchmark 里的意义,是代表“通用核 + 标准 RVV 1.0”这条路线。它的向量性能不是靠专用矩阵单元赢来的,而是靠经典的向量寄存器文件、多发射数据通路、编译器自动向量化共同作用。这种方案的优势是通用性好,标量负载和向量负载可以共享一套成熟软件生态;劣势则是面对极端算力需求时,不如专用 AI 加速单元那么“省电高效”。
2.2 Lanxin LX500:面向边缘嵌入式的向量实现
Lanxin LX500 的公开资料相对有限。从命名和同类产品的惯例推断,它面向的应该是边缘计算、智能硬件这类偏嵌入式场景,处理器的能效比是重要指标。这类芯片的向量计算需求通常集中在 INT8/FP16 推理、语音信号处理、传感器数据处理等中等精度、高吞吐任务上。
LX500 出现在 RVV Benchmark 中,大家关心的往往不是它的理论峰值有多高,而是它在受限的功耗和面积预算内,能把 RVV 的指令执行效率做到什么程度。这类芯片常见的做法是采用较小的 VLEN(比如 128 位),配合较低主频,换取更好的能效。这种设计在峰值 FLOPs 上肯定打不过高性能应用核,但放到具体场景里,可能反而是“够用且省电”的务实选择。
2.3 Epic Semi Contrail AIx:面向 AI 计算的设计思路
Epic Semi Contrail AIx 从产品命名看,AIx 指向明确的 AI 加速方向,Contrail 应该是产品系列代号。公开信息同样有限,结合名称推断,它更侧重 AI 推理加速,与通用核不同,其向量或矩阵计算单元的设计可能更偏专用,比如加入对 INT8 推理的特殊支持、更大的片上内存、更高效的数据搬运机制。
放到 RVV Benchmark 这个框架下,Contrail AIx 这类芯片会有一个有意思的现象:如果 benchmark 只测标准 RVV 整数和浮点指令,它的优势可能不会太突出;但如果 benchmark 覆盖了更接近真实 AI 负载的算子(比如卷积、矩阵乘),它的特殊设计就会体现出来。所以评审这类芯片的 RVV 数据时,一定要看测试负载是否覆盖了目标应用的真实计算形态。
2.4 它们为什么可以放在一起比
这三款芯片放到同一份 RVV Benchmark 里,真正的共同点不是性能级别,而是它们都宣称支持 RVV,并且都希望借助向量扩展来提升特定场景的计算能力。这正是 RVV Benchmark 最重要的价值:它提供了一套与厂商无关的统一度量标准。
有了这套标准,不同设计路线的芯片可以站在同一个擂台上跑相同代码,比的不是“谁宣传得好”,而是“谁能让硬件把指令执行得更高效”。当然,这也引出了那个经典问题:光有标准还不够,还得看怎么测。这正好是第 4 章要展开的内容。
3. RVV 核心概念:VLEN、DLEN、LMUL 与版本分裂
要读懂任何一份 RVV Benchmark,先要搞懂几个长期被混淆的概念。很多性能异常,根源不在硬件不够强,而是对这些概念理解错了。
3.1 RVV 是什么
RVV 是 RISC-V 向量扩展的简称,全称 RISC-V Vector Extension。它定义了一组新的向量寄存器和向量指令,让处理器可以在一条指令里对多个数据元素执行相同的运算。这种“单指令多数据”(SIMD)模式是目前 AI 推理、信号处理、多媒体编解码等负载提升性能的核心手段。
RVV 规范本身定义了从寄存器位宽、指令编码到异常处理的完整体系。最终用户通常不直接面对这些底层细节,而是通过编译器(GCC、LLVM)或内建函数(intrinsics)来使用向量能力。但理解寄存器位宽等基本参数,对分析和调优性能非常重要。
3.2 VLEN:向量寄存器长度
VLEN 是架构定义的向量寄存器位宽,单位是比特。RVV 规范没有强制要求所有处理器使用统一的 VLEN,128 位、256 位、512 位甚至更高都是合法的设计选择。这意味着同样是“支持 RVV”的芯片,它的向量寄存器可能宽窄不一。
VLEN 对 benchmark 的影响体现在软件层面:编译器为某个 VLEN 生成的代码,在另一个 VLEN 的芯片上运行,循环展开策略和寄存器分配都会变。RVV 规范通过动态向量长度机制(vsetvli 指令)来吸收这种差异,但实际性能仍然会因为 VLEN 不同而产生显著变化。
3.3 DLEN:数据通路宽度
DLEN 是处理器实际向量数据通路的宽度。这是 RVV 性能分析里最容易被忽略的参数,也是我最想强调的概念。
DLEN 可能等于 VLEN,也可能小于 VLEN。比如一个处理器宣称 VLEN=512 位,但内部向量数据通路只有 128 位,那么一条 512 位的向量指令会被拆分成多条 128 位的微操作,需要多个周期才能完成。软件工程师看到的是一个 512 位寄存器,但硬件每个周期只能处理 128 位。
这个设计由面积和功耗预算决定。大 VLEN 的优势是软件编程模型更简洁,循环迭代次数更少;但真实计算吞吐量由 DLEN 决定。所以比较两颗芯片时,不能只看“支持 256 位还是 512 位向量”,更要看它的 DLEN 是多少。在大多数 RVV Benchmark 里,DLEN 比 VLEN 更能体现真实计算能力。
3.4 LMUL:寄存器组倍数
LMUL 是向量寄存器组倍数,取值范围通常是 1、2、4、8。它表示一条向量指令可以同时使用多少个向量寄存器。LMUL 越大,单条指令能处理的数据元素越多,循环开销越小,但寄存器压力也越大。
LMUL 不是越高越好。LMUL=8 时,一条指令占用 8 个向量寄存器,如果算法本身的数据依赖较强,反而可能因为寄存器不足导致性能下降。编译器通常会根据循环结构自动选择合适的 LMUL,但自动选择不一定最优,intrinsic 编程时可以手动控制。这也是 RVV Benchmark 中一个常见的调优变量。
3.5 RVV 1.0 与 0.7.1 的兼容性问题
RVV 规范经历了从草案到正式版的演进。2021 年,RVV 1.0 获批成为正式版本。但在此之前,很多芯片基于 0.7.1 草案版本设计,两个版本在指令编码、语义、部分指令行为上不兼容。
这意味着,基于 0.7.1 写的向量汇编或 intrinsics 代码,直接拿到 1.0 的芯片上可能无法编译或运行。工具链也需要配对:旧工具链不支持 1.0 的指令编码,新工具链对 0.7.1 的支持也在逐步移除。所以评测任何 RVV 性能前,第一件事是确认芯片支持的是哪个版本。
当前的主流方向是 RVV 1.0,新的芯片设计和工具链基本都以 1.0 为基准。但存量市场里 0.7.1 的芯片和设备仍然存在,评估时如果忽略这一点,很可能出现“拿着 1.0 的优化代码,在 0.7.1 的机器上跑出异常结果”的情况。
3.6 SEW 与 VLMAX
SEW(Standard Element Width)是向量元素位宽,常见取值有 8、16、32、64 位。向量寄存器能容纳的元素数量由 VLMAX 决定,计算公式是:
VLMAX = VLEN × LMUL / SEW比如 VLEN=128、LMUL=1、SEW=32 时,VLMAX=4,意味着一条指令一次最多处理 4 个 32 位元素。如果要处理更多元素,需要循环迭代或增大 LMUL。
这个参数对 benchmark 测试设计影响很大。同一个算法,用 FP32 还是 FP16,用 LMUL=1 还是 LMUL=4,最后测出来的吞吐量可能有数倍差距。所以一份规范的 RVV Benchmark 报告,必须同时说明测试时的 VLEN、DLEN、LMUL 和 SEW。
4. RVV Benchmark 的评测维度与测试矩阵设计
理解了基础概念,再看 Benchmark 本身。RVV Benchmark 不是单个测试程序,而是一整套可以度量处理器向量计算能力的测试矩阵。设计这套矩阵时,我建议从以下几个维度入手。
4.1 访存密集负载:SAXPY、向量拷贝、向量求和
这类负载的特点是“算得少、搬得多”。以 SAXPY 为例:
y[i] = a * x[i] + y[i]每个元素只做一次乘法和一次加法,但要从内存读取 x 和 y,再写回 y。这种负载的瓶颈几乎永远在内存带宽,向量计算单元反而是空闲的。
这类测试的价值在于验证处理器的 Load/Store 单元的吞吐能力,以及缓存系统对连续向量访问的支持程度。两颗 DLEN 相同的芯片,如果访存系统设计不同,在 SAXPY 测试上的表现可能有明显差异。
4.2 计算密集负载:矩阵乘法、卷积、FFT
矩阵乘法(GEMM)是最典型的计算密集负载。每个输出元素需要执行多次乘加运算,数据复用率高,对向量计算单元的 FMA(乘加指令)吞吐要求极高。
这类测试能暴露处理器真正的“算力天花板”,也更容易受 DLEN、编译器代码生成质量、寄存器分配策略的影响。GEMM 类 benchmark 通常在实现上有很多优化空间,比如分块(tiling)、数据对齐、LMUL 选择,所以测试代码的实现质量对结果影响非常大。
4.3 混合负载:真实算子片段
比纯 microbenchmark 更接近实际的是混合负载测试。比如从某个 AI 模型里抽出一个卷积层、一个激活函数、一个池化层,组合成一段测试代码。这种测试的好处是能反映真实应用中的指令混合比例、数据流形态和缓存访问模式。
对于 AI 芯片(比如 Epic Semi Contrail AIx 这类强调 AI 计算的芯片),混合负载测试往往比纯 SAXPY 更能体现设计优势。
4.4 测试矩阵的维度
设计 RVV Benchmark 测试矩阵时,建议覆盖以下变量:
| 变量 | 说明 | 推荐覆盖范围 |
|---|---|---|
| 数据类型 | SEW 位宽 | FP64 / FP32 / FP16 / INT32 / INT8 |
| LMUL | 寄存器组倍数 | 1 / 2 / 4 / 8 |
| 数组规模 | 数据量大小 | 小(L1 内)、中(L2)、大(内存) |
| 访问模式 | 数据布局 | unit-stride、strided、indexed |
| 负载类型 | 计算形态 | SAXPY、GEMM、copy、reduction |
只有覆盖了这几个维度, benchmark 结果才能回答“这颗芯片在不同场景下分别表现如何”,而不是只给出一个笼统的“性能得分”。
4.5 自动向量化与 intrinsic 的区分
评估 RVV 性能时,还应该区分两种测试方式:一种是用编译器自动向量化(直接写普通 C 循环,靠编译器生成向量指令);另一种是手写 intrinsic 或汇编。两者之间的性能差距,反映的其实是工具链成熟度和软件适配成本。
对开发者更有参考价值的往往是自动向量化的结果,因为这意味着存量 C 代码能不能低成本迁移到目标芯片上。如果自动向量化效果很差,可能不是硬件不行,而是编译器对该芯片的调优还不充分。这也是 RVV Benchmark 里一个容易被忽视的隐含信息。
5. 环境搭建与工具链准备
读 benchmark 是第一步,自己动手跑一遍才是真正掌握这套方法论的方式。下面以一个常见的最小流程为例,说明如何从零开始搭一套 RVV 性能测试环境。
5.1 硬件或模拟器选择
最理想的情况是在目标芯片的真实开发板上运行测试。但考虑到获取开发板的门槛,也可以先用 QEMU 模拟器或 Spike 指令集模拟器验证功能正确性。需要特别强调的是:模拟器的性能数据对真实硬件几乎没有参考价值,它只能帮助你确认代码逻辑和汇编生成的正确性。
如果只有 QEMU,推荐用virt机器模型,并显式开启向量扩展。QEMU 的 RISC-V CPU 默认支持向量扩展,但 VLEN 可以通过参数指定:
qemu-system-riscv64 \ -machine virt \ -cpu rv64,v=true,vlen=128,elen=64 \ -m 2G \ -nographic \ -kernel vmlinux \ -drive file=rootfs.ext4,format=raw,id=hd0 \ -device virtio-blk-device,drive=hd0其中v=true表示开启向量扩展,vlen=128指定向量寄存器长度为 128 位。这只是功能验证环境,不要用它来评估性能。
5.2 工具链安装
RVV 1.0 支持在 GCC 12 及更高版本、LLVM 16 及更高版本中已经比较成熟。版本请以实际项目为准,建议优先使用较新的稳定版本。如果目标平台是 Linux 环境,可以直接使用发行版自带的交叉编译工具链,也可以从 RISC-V 官方工具链源码编译。
编译时,关键参数是-march:
rv64gc:通用标量指令集,不含向量扩展,用于对照组。rv64gcv:开启向量扩展,使用默认 VLEN。rv64gcv_zvl128b:要求处理器至少支持 VLEN>=128 位的向量寄存器。rv64gcv_zvl256b:要求处理器至少支持 VLEN>=256 位。
例如:
riscv64-unknown-linux-gnu-gcc \ -O3 \ -march=rv64gcv_zvl128b \ -mabi=lp64d \ -o test_saxpy_rvv test_saxpy.c5.3 确认目标设备是否支持 RVV
在真实设备上运行前,先确认内核是否识别到向量扩展。RISC-V Linux 环境下可以查看/proc/cpuinfo:
grep 'riscv,isa' /proc/cpuinfo如果输出中包含v,说明 CPU 支持向量扩展。例如:
riscv,isa = rv64imafdcv_zba_zbb_zbc_zbs其中rv64...v的v就代表 RVV。如果看到的是rv64imafdc,说明内核没有检测到向量支持,问题可能出在硬件、内核配置、QEMU 启动参数中的任意一处。
5.4 编写最小验证程序
环境准备好后,可以编译一个最简单的向量程序确认工具链和运行环境正常工作。下面是一个使用 intrinsics 的 SAXPY 实现。
// 文件路径:saxpy_rvv.c #include <riscv_vector.h> #include <stddef.h> void saxpy_rvv(size_t n, float a, const float *x, float *y) { size_t vlmax = __riscv_vsetvlmax_e32m1(); size_t k = 0; for (; k < n; k += vlmax) { size_t vl = __riscv_vsetvl_e32m1(n - k); vfloat32m1_t x_vec = __riscv_vle32_v_f32m1(x + k, vl); vfloat32m1_t y_vec = __riscv_vle32_v_f32m1(y + k, vl); y_vec = __riscv_vfmacc_vf_f32m1(y_vec, a, x_vec, vl); __riscv_vse32_v_f32m1(y + k, y_vec, vl); } }这段代码的逻辑是:先读取当前硬件支持的最大向量长度vlmax,然后在循环里用__riscv_vsetvl_e32m1计算当前迭代实际可处理的元素数(最后一次迭代可能不足vlmax)。__riscv_vle32_v_f32m1从内存加载浮点向量,__riscv_vfmacc_vf_f32m1执行向量乘加运算,最后用__riscv_vse32_v_f32m1写回内存。
对应的标量版本:
// 文件路径:saxpy_scalar.c #include <stddef.h> void saxpy_scalar(size_t n, float a, const float *x, float *y) { for (size_t k = 0; k < n; k++) { y[k] = a * x[k] + y[k]; } }编译命令:
riscv64-unknown-linux-gnu-gcc -O3 -march=rv64gcv_zvl128b -mabi=lp64d -static -c saxpy_rvv.c -o saxpy_rvv.o riscv64-unknown-linux-gnu-gcc -O3 -march=rv64gc -mabi=lp64d -static -c saxpy_scalar.c -o saxpy_scalar.o两个文件编译成功后,说明工具链和 CPU 的 RVV 支持基本没有问题。接下来可以进入真正的性能测试流程。
6. 最小可运行示例:SAXPY 向量化与标量对比
前面只是验证了编译和基本执行。要做性能对比,需要把测试代码封装成一个可独立运行的程序,并加上计时逻辑。
6.1 加入计时逻辑的完整示例
为了避免 QEMU 的墙钟时间带来的误导,这里采用clock_gettime做用户态计时,在真实硬件上可以反映实际耗时。如果你在 Linux 真实设备上运行,这个方法是可靠的。
// 文件路径:bench_saxpy.c #include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> #include "saxpy_rvv.c" #include "saxpy_scalar.c" static double now_us(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double)ts.tv_sec * 1e6 + (double)ts.tv_nsec / 1e3; } int main(int argc, char **argv) { if (argc < 2) { printf("usage: %s <n>\n", argv[0]); return 1; } size_t n = strtoul(argv[1], NULL, 10); float *x = (float *)aligned_alloc(64, n * sizeof(float)); float *y1 = (float *)aligned_alloc(64, n * sizeof(float)); float *y2 = (float *)aligned_alloc(64, n * sizeof(float)); for (size_t i = 0; i < n; i++) { x[i] = 1.0f; y1[i] = 0.0f; y2[i] = 0.0f; } // 预热,避免冷缓存和 page fault 影响 saxpy_scalar(n, 2.0f, x, y1); saxpy_rvv(n, 2.0f, x, y2); const int repeat = 50; double t0 = now_us(); for (int i = 0; i < repeat; i++) { saxpy_scalar(n, 2.0f, x, y1); } double t_scalar = (now_us() - t0) / repeat; t0 = now_us(); for (int i = 0; i < repeat; i++) { saxpy_rvv(n, 2.0f, x, y2); } double t_rvv = (now_us() - t0) / repeat; double mem_bytes = 3.0 * n * sizeof(float); // 读x, 读y, 写y printf("n=%zu\n", n); printf("scalar: %.3f us, %.3f GB/s\n", t_scalar, mem_bytes / t_scalar / 1e3); printf("rvv: %.3f us, %.3f GB/s\n", t_rvv, mem_bytes / t_rvv / 1e3); printf("speedup: %.2fx\n", t_scalar / t_rvv); free(x); free(y1); free(y2); return 0; }6.2 编译和运行
编译时,需要特别注意:saxpy_rvv.c这个文件依赖riscv_vector.h,所以必须使用支持向量扩展的编译器,并且-march要正确传递。一个比较稳妥的组合命令是:
riscv64-unknown-linux-gnu-gcc \ -O3 \ -march=rv64gcv_zvl128b \ -mabi=lp64d \ -static \ -o bench_saxpy bench_saxpy.c运行:
./bench_saxpy 1048576数组大小取1048576(即 2^20 个 float,约 4 MB),目的是让数据量超过 L1/L2 缓存,测试更接近真实内存带宽受限的情况。
6.3 如何判断运行是否正确
除了看时间,还要验证结果的正确性。可以在main中加一段比较代码,确认y1和y2在误差允许范围内一致:
int error = 0; for (size_t i = 0; i < n; i++) { if (y1[i] != y2[i]) { error = 1; break; } } if (error) { printf("ERROR: result mismatch!\n"); return 2; }注意浮点运算的加法顺序可能不同,所以更稳妥的做法是设置一个很小的容差:
float diff = y1[i] - y2[i]; if (diff < 0) diff = -diff; if (diff > 1e-5) { error = 1; break; }如果出现结果不一致,优先检查-march是否同时开启了向量扩展,以及是否在编译时混用了不同版本的riscv_vector.h头文件。
7. 运行结果验证与性能数据解读方法
跑出几组时间数据只是第一步,接下来要判断这些数据是否可信、是否正常。这个环节最容易出错,也最容易被 benchmark 材料里精心挑选的数字误导。
7.1 不要直接用墙钟时间做结论
墙钟时间受频率调节、缓存状态、系统负载影响很大。更可靠的指标是“周期数”或“每元素周期数”。RISC-V 有一个用户态可读的cycle计数器,但在部分平台上读取权限受限。如果目标环境允许,可以用内联汇编读取:
static inline unsigned long read_cycles(void) { unsigned long cycles; asm volatile("rdcycle %0" : "=r"(cycles)); return cycles; }然后计算基准测试前后 cycle 的差值,再除以处理元素数,得到每元素周期数。这个数值排除了主频波动的影响,更适合横向比较。
如果rdcycle在目标系统上被禁用(用户态无法读取),可以退而求其次使用rdtime,或者统一使用clock_gettime,前提是记录 CPU 频率并做换算。最稳妥的方式还是记录测试时的固定频率设置,确保所有对比组在相同频率下运行。
7.2 SAXPY 结果的典型特征
SAXPY 是访存密集负载,性能上限由内存带宽决定,而不是向量计算单元。假设你的系统内存带宽是 20 GB/s,需要搬运的数据总量是3 * n * sizeof(float),那么理论最短时间大约为:
time_min = 3 * n * 4 / 20000 (us)如果测试出来的 SAXPY 时间和这个理论值接近,说明向量实现已经接近访存极限,没有多少优化空间了。如果远高于理论值,要么内存带宽更高,要么数据量太小没有跑出内存的稳态性能。
所以 SAXPY 加速比通常不会特别夸张,甚至在部分芯片上只有 1.2 到 1.5 倍。这不是 RVV 没用,而是访存瓶颈限制了收益。真实项目里如果遇到这种情况,优化重点应该转向数据复用,而不是继续抠向量指令。
7.3 矩阵乘法的预期输出特征
与 SAXPY 不同,矩阵乘法是计算密集负载。理论上一个支持 FMA 的向量单元应该能跑出接近峰值 FLOPs 的性能。这时候需要看的指标是“每周期 FLOPs”或“每秒 FLOPs”。
如果矩阵乘法结果远低于理论峰值,优先怀疑分块和缓存优化不到位,而不是芯片不行。这也是为什么很多 RVV Benchmark 材料里只拿 GEMM 说事,因为 GEMM 对优化程度极其敏感,同一颗芯片,算法优化得好坏可以让结果相差数倍。
7.4 每一组数据都要记录测试条件
无论你在自己的板子上跑,还是审阅别人的 benchmark 材料,一份可信的数据必须包含下面这些信息:
| 测试条件 | 原因 |
|---|---|
| CPU 型号和主频 | 频率影响绝对性能 |
| RVV 规范版本 | 0.7.1 与 1.0 代码不通用 |
| 工具链版本 | GCC/LLVM 版本影响代码生成质量 |
| 编译选项 | -O3、-march、-mabi必须一致 |
| VLEN / DLEN | 决定峰值算力 |
| 测试数据规模 | 小数组测缓存,大数组测内存带宽 |
| 预热和重复次数 | 排除冷启动影响 |
| 内存带宽规格 | 判断 SAXPY 类测试是否合理 |
缺少这些信息,跑分再高也不能作为选型依据。
8. RVV Benchmark 常见误区与排查清单
做 RVV 性能测试时,常见的坑很多。下面这张表是实战中总结的高频问题,可以贴在墙上的那种。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
编译报错:riscv_vector.h不存在 | 工具链版本过旧,或不支持 RVV 1.0 | 检查编译器版本,确认-march包含v | 升级到 GCC 12+/LLVM 16+,重新编译 |
| 编译通过但运行报“非法指令” | 编译目标包含的指令超出了硬件实际支持范围(例如 VLEN 不匹配) | 查看/proc/cpuinfo的riscv,isa,确认实际支持的扩展 | 按硬件实际支持的-march重新编译 |
| 向量版本比标量版本还慢 | 测试数据量太小,向量化固定开销大于收益 | 增大数组规模并加入预热循环 | 使用 L1/L2 几倍以上的数据量重新测 |
| SAXPY 加速比很低 | 访存带宽是瓶颈,向量计算单元没有满负荷 | 计算理论带宽,对比实际带宽 | 数据量设为远大于缓存,并确认内存频率配置一致 |
| 跑分结果忽高忽低 | CPU 频率动态调节,或缓存状态未控制 | 记录测试时频率,使用cycle计数,增加预热 | 固定 CPU 频率,多次取中位数 |
| 同一条代码在真机和 QEMU 上结果差异巨大 | QEMU 是功能模拟,不反映硬件时序 | 不要在模拟器上做性能结论 | 用真机做性能评估,QEMU 只做功能验证 |
| 自动向量化没生效 | 编译器没有把循环识别为可向量化 | 查看生成的汇编是否有向量指令,检查编译器优化报告 | 使用-fopt-info-vec查看自动向量化诊断 |
| GEMM 性能远低于理论峰值 | 分块、对齐、LMUL 选择不合理 | 检查是否使用aligned_alloc,尝试不同 LMUL | 参考-O3 -funroll-loops以及分块优化 |
自动向量化诊断的编译命令示例:
riscv64-unknown-linux-gnu-gcc \ -O3 \ -march=rv64gcv_zvl128b \ -mabi=lp64d \ -fopt-info-vec \ -c saxpy_rvv.c -o saxpy_rvv.o如果输出里有类似LOOP VECTORIZED的信息,说明编译器成功向量化;如果没有任何提示,说明编译器未能将循环转换为向量代码,需要检查数据依赖和循环结构。
另外还有一个很容易被忽略的问题:intrinsic 头文件的版本不匹配。<riscv_vector.h>是编译器自带的,不同编译器版本对应的 intrinsic API 有细微差异。如果代码在某个版本上编译通过,升级编译器后却报错,优先检查 API 名称是否变化(例如vsetvlmax在不同版本中的命名差异)。
9. 工程选型与迁移建议
讲了这么多原理和方法,最终还是要落回到工程决策。如果你正在评估某款 RISC-V 处理器的向量性能,或者准备把现有算法迁移到 RVV 平台,有几点建议可以参考。
9.1 先明确负载类型,再挑 benchmark 数据
SAXPY 类访存密集数据,适合判断内存子系统;GEMM 类计算密集数据,适合判断算力峰值。如果你的核心应用是信号处理,应该重点看复数运算和 FFT 类测试;如果是 AI 推理,卷积和矩阵乘数据的参考价值最高。任何一份 RVV Benchmark 材料,如果只给一个总分,而不区分负载类型,它的参考价值都要打折扣。
9.2 工具链版本比硬件性能更容易成为瓶颈
在实际迁移中,真正卡住开发进度的往往不是芯片本身,而是工具链。RVV 1.0 在 GCC 和 LLVM 上的支持成熟度是逐步提升的,不同版本生成的向量代码质量差别很大。建议在开始优化之前,先跑通一套最小基准,记录不同编译选项的性能差异。这样后续优化效果有据可查。
9.3 自动向量化优先,intrinsic 兜底
对存量 C 代码,先尝试编译器自动向量化,这是成本最低的迁移路径。只有当自动向量化效果不理想,或者需要精细控制数据布局时,再考虑引入 intrinsics。引入 intrinsics 意味着代码与具体芯片的耦合度上升,后续更换芯片时维护成本会增加。两者需要做好权衡。
9.4 建立自己的性能基线库
与其每次都去搜公开 benchmark 报告,更建议团队内部维护一个小的性能基线库,包含针对不同负载类型的测试程序和对应的编译配置。每次评估新芯片时,用同一套代码重新编译、运行、记录数据。这样积累半年后,你手里的横向对比数据会是最有说服力的选型依据。
9.5 关注功耗与能效,而不只是峰值性能
峰值 FLOPs 是能力上限,但真实系统里功耗约束往往更重要。两款芯片峰值性能相近,功耗可能相差很大。RVV Benchmark 结果如果能结合功耗数据(每瓦性能),评价会更全面。如果没有功耗测试条件,至少要在同一功耗模式下进行对比,避免“高性能模式 vs 节能模式”这种不公平比较。
RVV Benchmark 不是一个“跑分比赛”,而是一套帮助开发者判断“硬件能力能否兑现”的工程工具。从 SiFive P870 的通用核路线,到 Lanxin LX500 的边缘能效路线,再到 Epic Semi Contrail AIx 的 AI 加速路线,三款芯片放在同一套 RVV 基准框架下,真正可比的是它们各自的设计取舍和软件生态成熟度。建议你先跑通文中的 SAXPY 示例,再连同矩阵乘法和访存测试一起,建立一套属于自己的小程序集。这样以后无论评估哪款 RISC-V 芯片,你手里都有一份可以横向对照的第一手数据,而不是依赖厂商或评测机构提供的结果。