
做 RISC-V 向量性能评估时最容易踩的坑就是“只敢看纸面参数不敢上真数据”。尤其是 SiFive P870、Lanxin LX500、Epic Semi Contrail AIx 这几类支持 RVV 1.0 的处理器芯片手册上写的峰值 FLOPS 和实际跑起来的吞吐差出一大截根本不是新鲜事。本文就以这三款处理器为背景整理一套完整的 RVV Benchmark 实操方法覆盖概念、环境、工具、微基准代码、数据解读和常见问题帮你从零搭起一套可复用的向量性能评测流程。1. RVV、Benchmark 与目标处理器1.1 RVV 是什么RVV 是 RISC-V Vector Extension 的缩写官方名称是 RISC-V 向量扩展规范。它定义了一组寄存器、指令和编程模型让 CPU 能够以“向量”为单位处理数据而不是一条指令只处理一个标量。RVV 1.0 规范有几个关键概念需要先建立印象VLEN向量寄存器长度硬件决定常见的有 128-bit、256-bit、512-bit。ELEN向量可以支持的最大元素位宽一般支持 8/16/32/64。LMUL向量长度乘数表示单个向量指令使用的寄存器组大小。vsetvl动态配置向量长度和 LMUL 的指令。和 ARM NEON 或 AVX 有一个很核心的区别RVV 的向量长度对软件是“动态可感知”的。程序可以在运行时通过 vsetvl 查询硬件支持的最大向量位宽然后动态调整循环步长。也就是说同一个二进制在不同 VLEN 的芯片上都能运行只不过性能不同。这也是做 RVV Benchmark 时最容易忽略的点如果不熟悉 RVV 的运行时调整机制很容易把代码写死成某个固定宽度换硬件后性能直接崩掉。1.2 为什么要做 RVV Benchmark芯片厂商或者开源处理器 IP 都会给出理论峰值算力但这只是“能不能达到”的上限不是“实际能跑多少”的承诺。RVV Benchmark 的目的是回答几个实际问题这款处理的向量单元实际吞吐如何宣称的 TOPS/FLOPS 有没有缩水。自动向量化编译器能不能用满硬件能力需要手写内联汇编吗在不同数据宽度FP32、FP16、INT8下性能变化符合预期吗真实负载中内存带宽、缓存、指令发射瓶颈会造成多大损耗这些问题不能靠看规格书解决只能靠跑分。1.3 三款处理器的定位差异围绕项目标题中的三款处理器先简单梳理它们的产品定位方便后续针对不同平台调整测试策略。SiFive P870RISC-V 高性能应用处理器核心。定位偏向于数据中心、边缘 AI、汽车等场景强调多核扩展和较高单核性能。它的向量单元支持 RVV 1.0是市面上很有代表性的高性能 RVV 核心。Lanxin LX500国内蓝芯微的处理器核心同样面向 AI 边缘计算、智能座舱等领域。它支持 RVV 1.0评测时需要关注它的小核/大核组合、缓存设计和向量吞吐之间的匹配度。Epic Semi Contrail AIxEpic Semiconductor 的 AI 加速类处理器方案。这类芯片往往除了通用 RISC-V 核心还有专门承接矩阵运算的硬件模块。RVV Benchmark 测出的既是通用核心的向量能力也是整套异构方案访存和调度能力的体现。有一点必须说明不同处理器之间做“纸面对比”没有意义。真正有参考价值的是在统一 Benchmark 方法、统一编译器版本、统一工作负载的前提下测出可复现的横向数据。2. 测试环境与工具链准备2.1 硬件与模拟器选择做 RVV Benchmark至少有三类环境真机开发板/服务器例如已经有 SiFive P870 的评估板或者集成 Lanxin LX500 的 SoC 平台。真机测试最准确但成本高、环境复用难。QEMU 模拟器适合功能调试和初步性能排序。QEMU 支持-cpu rv64, vtrue之类的参数开启向量扩展但模拟出来的性能不代表芯片真实性能。RISC-V 指令级模拟器如 Spike用于功能验证和 profile不建议直接用来做性能基准。如果同时具备真机和模拟器建议先用模拟器调试代码正确性再拿到真机上跑性能。如果只有 QEMU可以做功能测试和算法验证但不能替代真实 Benchmark 数据。2.2 工具链版本选择RVV 1.0 的支持从 GCC 12 之后逐步稳定GCC 13/14 的自动向量化能力明显增强。Clang/LLVM 也从较早期就开始支持 RVV但在不同版本上内建函数命名和寄存器分配策略有差异。建议使用较新的工具链# GCC 12 以上推荐 13.2 或 14 riscv64-unknown-linux-gnu-gcc --version # Clang 17 以上 clang --version如果你的发行版没有现成的 riscv 交叉编译器可以下载 RISC-V 官方工具链源码编译或者使用厂商 SDK 自带的工具链。这里要提醒一个现实问题不同处理器厂商的 SDK 可能内置不同分支的 GCC编译出的向量指令调度可能有差异。为了横向对比应该尽量统一编译器版本和编译参数。2.3 内核与系统配置Linux 内核需要开启 RISC-V 向量支持。不同内核版本的支持情况不同一般需要# 查看当前 CPU 是否暴露向量扩展 grep -w v /proc/cpuinfo # 如果返回 v 则代表 RVV 硬件特性已暴露给用户态某些内核会用riscv_v_ctx或CONFIG_VECTOR之类的配置项控制向量支持。如果/proc/cpuinfo看不到v需要重新配置内核开启向量支持。这里有一个常见的坑RVV 上下文切换在早期内核实现里有较大开销。如果 Benchmark 的进程频繁被调度测量数据会被上下文切换干扰。跑分前建议绑核taskset -c 4 ./saxpy_bench3. RVV Benchmark 的核心维度与负载分类3.1 性能指标选择做 RVV Benchmark 不能只跑一个“总分”。建议至少收集以下指标有效算力单位时间完成的浮点/定点运算次数单位是 GFLOPS 或 GOPS。实际加速比相对相同 C 代码、无向量编译选项的标量版本的加速比。带宽利用率通过 STREAM 类测试或向量 load/store 测试估算访存压力。算力效率实测峰值除以理论峰值反映硬件设计的真实效率。指令占比通过 perf 统计 vector 指令数量观察内核是否真的有向量指令发射。3.2 负载分类Benchmark 工作负载大致分为三层微基准Microbenchmark如向量加法、SAXPY、点积、矩阵乘的单个 kernel。用于观察指令发射、寄存器带宽和访存带宽的上限。基础计算库如 OpenBLAS 的 gemm、FFTW 的 FFT。用于观察自动向量化和库优化水平贴近科学计算场景。AI 推理样例如 ResNet-50、MobileNet 的卷积、全连接、激活函数。用于观察真实负载中向量单元的利用率和内存瓶颈。如果评估对象是 AI 加速芯片第三层尤其重要但也要注意这类芯片可能在通用核心之外还有专属加速器此时测出的 RVV 性能不代表整颗 AI 芯片的推理能力。3.3 测试矩阵设计为了全方位了解处理器建议设计一个二维测试矩阵维度候选值数据类型FP32、FP64、FP16、INT8核心运算SAXPY、Dot Product、MatMul、Conv数据规模小L1 常驻、中L2 常驻、大内存带宽受限编译器选项-O3、-O3 -marchrv64gcv、-O3 -marchrv64gcv_zvfh每个组合至少跑 3 次取中位数避免单次噪声。4. 完整实战从微基准到数据可视化下面以 SAXPY、点积和矩阵乘三个典型 kernel 为例演示一套可以直接复用的 RVV Benchmark 流程。示例基于 Linux 用户态 C 程序交叉编译后在目标 RISC-V 平台上运行。4.1 检查硬件与工具链在目标板上先确认 RVV 特性cat /proc/cpuinfo # 如果输出包含 v 说明 RVV 1.0 可用 # 如果有 rvv_zvfh 这类字符串说明还支持半精度向量扩展然后确认工具链支持riscv64-unknown-linux-gnu-gcc -marchrv64gcv -O3 -S -o /dev/null -x c /dev/null - int main(){}如果这条命令能正常执行说明工具链可以编译 RVV 代码。4.2 SAXPY 微基准SAXPY 是y a * x y的向量操作是判断向量吞吐和访存带宽的经典例子。文件路径bench/saxpy.c#include stdio.h #include stdlib.h #include stdint.h #include time.h #include math.h #define N 1 20 static inline double now_sec(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, ts); return ts.tv_sec ts.tv_nsec * 1e-9; } void saxpy_scalar(float *y, float *x, float a, size_t n) { for (size_t i 0; i n; i) { y[i] a * x[i] y[i]; } } void saxpy_autovec(float *y, float *x, float a, size_t n) { #pragma omp simd for (size_t i 0; i n; i) { y[i] a * x[i] y[i]; } } int main(int argc, char *argv[]) { float *x aligned_alloc(64, N * sizeof(float)); float *y aligned_alloc(64, N * sizeof(float)); if (!x || !y) return 1; for (size_t i 0; i N; i) { x[i] (float)i; y[i] (float)(i 255); } float a 2.0f; double total 0.0; // 预热 saxpy_autovec(y, x, a, N); for (int r 0; r 10; r) { double t0 now_sec(); saxpy_autovec(y, x, a, N); double t1 now_sec(); total (t1 - t0); } double avg total / 10.0; double bytes 3.0 * N * sizeof(float); double flops 2.0 * N; printf(SAXPY autovec avg time: %.6f s\n, avg); printf(SAXPY bandwidth: %.2f GB/s\n, bytes / avg / 1e9); printf(SAXPY throughput: %.2f GFLOP/s\n, flops / avg / 1e9); free(x); free(y); return 0; }编译时需要注意riscv64-unknown-linux-gnu-gcc -O3 -marchrv64gcv -fopenmp bench/saxpy.c -o saxpy_bench为了提高定位精确度可以用-Rpassloop-vectorize观察自动向量化是否生效但 GCC 不直接支持这个选项Clang 使用该参数clang --targetriscv64-unknown-linux-gnu -O3 -marchrv64gcv -Rpassloop-vectorize bench/saxpy.c -o saxpy_bench如果自动向量化失败可以尝试手写 RVV intrinsic 版本。4.3 手写 RVV intrinsic 点积点积适合观察向量寄存器累加和数据复用。这里给出一个使用 RVV intrinsic 的版本注意vsetvlmax是运行时查询最大向量长度的常见写法。文件路径bench/dotprod.c#include riscv_vector.h #include stdio.h #include stdlib.h #include stdint.h #include time.h #define N (1 20) static inline double now_sec(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, ts); return ts.tv_sec ts.tv_nsec * 1e-9; } float dotprod_rvv(float *a, float *b, size_t n) { size_t avl n; vfloat32m4_t va, vb; vfloat32m4_t vsum vfmv_v_f_f32m4(0.0f, avl); while (avl 0) { size_t vl vsetvl_e32m4(avl); va vle32_v_f32m4(a, vl); vb vle32_v_f32m4(b, vl); vsum vfmacc_vv_f32m4(vsum, va, vb, vl); a vl; b vl; avl - vl; } float sum vfmv_f_s_f32m4_f32(vfredusum_vs_f32m4_f32m1(vsum, vfmv_v_f_f32m1(0.0f, 1), 1)); return sum; } int main() { float *a aligned_alloc(64, N * sizeof(float)); float *b aligned_alloc(64, N * sizeof(float)); for (size_t i 0; i N; i) { a[i] 1.0f; b[i] 2.0f; } dotprod_rvv(a, b, N); // 预热 double total 0.0; for (int r 0; r 20; r) { double t0 now_sec(); float result dotprod_rvv(a, b, N); double t1 now_sec(); total (t1 - t0); if (result 0) printf(unexpected\n); } double avg total / 20.0; printf(dot product avg time: %.6f s\n, avg); printf(dot product throughput: %.2f GFLOP/s\n, 2.0 * N / avg / 1e9); return 0; }注意aligned_alloc需要-stdc11或更新标准riscv64-unknown-linux-gnu-gcc -O3 -marchrv64gcv -stdc11 bench/dotprod.c -o dotprod_bench运行后你会看到点积的吞吐通常高于 SAXPY因为它在更小的访存压力下做了更多计算。如果反过来说明内存带宽很可能是瓶颈。4.4 在一个文件里测试多个 kernel实际项目里建议把多个 kernel 放进一个可执行文件用命令行参数选择方便自动化测试。文件路径bench/rvv_bench.c结构示意int main(int argc, char *argv[]) { if (argc 2) { printf(usage: %s [saxpy|dotprod|matmul|copy]\n, argv[0]); return 1; } if (strcmp(argv[1], saxpy) 0) run_saxpy(); else if (strcmp(argv[1], dotprod) 0) run_dotprod(); else if (strcmp(argv[1], matmul) 0) run_matmul(); else if (strcmp(argv[1], copy) 0) run_copy(); else return 1; return 0; }这种设计的好处是后续接 CI 自动化时只需要一个参数切换测试项。4.5 运行与数据采集在目标板上运行./saxpy_bench ./dotprod_bench taskset -c 4 ./matmul_bench如果需要收集指令统计可以用 perfperf stat -e task-clock,cycles,instructions ./saxpy_bench perf stat -e rvv_retired.* ./dotprod_bench不过不同内核的 RAWV 事件名可能不同优先用perf list | grep -i vector查看当前内核支持的事件。4.6 数据整理与可视化使用 Python 整理测试结果比较不同配置的性能。下面这个脚本读取 CSV 格式绘制简单的柱状图。文件路径tools/plot_bench.pyimport csv import sys import matplotlib.pyplot as plt import numpy as np def load_csv(path): rows [] with open(path, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: rows.append(row) return rows def main(): if len(sys.argv) 2: print(usage: python3 plot_bench.py result.csv) sys.exit(1) rows load_csv(sys.argv[1]) names [r[kernel] for r in rows] flops [float(r[gflops]) for r in rows] fig, ax plt.subplots(figsize(8, 5)) x np.arange(len(names)) bars ax.bar(x, flops, width0.5, color#4C72B0) ax.set_xticks(x) ax.set_xticklabels(names, rotation30, haright) ax.set_ylabel(GFLOPS) ax.set_title(RVV Benchmark Result) for bar, val in zip(bars, flops): ax.text(bar.get_x() bar.get_width()/2, bar.get_height(), f{val:.2f}, hacenter, vabottom) plt.tight_layout() plt.savefig(rvv_bench_result.png, dpi150) print(saved to rvv_bench_result.png) if __name__ __main__: main()建议保存原始输出到文件./dotprod_bench result_dotprod.csv python3 tools/plot_bench.py result_dotprod.csv这样每次跑分都有记录后面写测试报告时不用临时补数据。5. 结果解读与横向对比方法5.1 理论峰值估算RVV 的 FP32 理论峰值大致可以用下面公式估算理论峰值(GFLOPS) 主频(GHz) × 每周期可执行向量元素数 × 发射端口数每周期可执行向量元素数又与 VLEN 相关。例如 VLEN256 时FP32 向量一次处理 8 个元素如果有一个能发射向量乘加指令的执行端口则理论峰值约为主频 × 8 × 2。这里的 2 表示乘加算作两次浮点运算。严格说这个估算还要看内核是否支持多发射、是否有独立的向量 ALU 和向量访存流水。这也是为什么理论峰值只能作为参考上限。5.2 对比不同处理器的方法当你想横向对比 SiFive P870、Lanxin LX500、Epic Semi Contrail AIx 时必须保证使用同一个编译器和编译参数。使用相同的 kernel 实现最好都走自动向量化路径或统一手写 intrinsic。设置相同的 CPU 频率管理模式关闭动态调频。使用相同的数据规模和对齐方式。测试脚本相同统计方法相同。如果满足这些条件你得到的数据才能放进同一张表里做对比。否则跑分差异可能来自编译器版本、内存布局或者频率调度而不是处理器本身。5.3 效率分析实测峰值与理论峰值的比值代表向量单元的利用效率。比如处理器理论峰值实测峰值效率P870XXXXXX%LX500XXXXXX%Contrail AIxXXXXXX%这个效率指标能直接反映硬件流水线的实际质量。效率低不一定是向量单元差也可能是内存带宽、缓存 miss 或指令发射约束拖了后腿。6. 常见问题与排查思路6.1 自动向量化没有生效问题现象常见原因解决思路编译后反汇编没有向量指令循环依赖、数据依赖、函数调用检查编译日志加-fopt-info或-Rpass分析汇编有向量指令但运行性能没有提升内存访问不对齐或频繁动态长度调整使用内存对齐分配减少 vsetvl 调用次数某些循环因为#pragma omp simd失败未向量化编译器版本较低或循环里有分支手写 intrinsic 或更新编译器GCC 中查看向量化信息riscv64-unknown-linux-gnu-gcc -O3 -marchrv64gcv -fopt-info-vec-all bench/saxpy.cClang 中查看clang --targetriscv64-unknown-linux-gnu -O3 -marchrv64gcv -Rpassloop-vectorize -Rpass-missedloop-vectorize -Rpass-analysisloop-vectorize bench/saxpy.c6.2 RVV 指令运行比预期慢问题现象常见原因解决思路小数据规模性能差向量启动和 vsetvl 开销大于收益增大数据规模或者对短循环走标量路径大数组性能差访存带宽达到瓶颈测试时使用阻塞分块观察带宽是否饱和多线程性能不线性扩展内存控制器或总线瓶颈绑核并分别统计各核数据观察内存带宽上限频繁调用 vsetvl循环内部反复设置 LMUL循环外提 vsetvl尽量减少动态配置次数6.3 编译报错错误信息常见原因解决思路unrecognized option --marchrv64gcv工具链过老升级 GCC/Clangunknown type name vfloat32m4_t缺少#include riscv_vector.h添加头文件V extension is not enabled内核未开启向量支持重新编译内核开启 CONFIG_VECTORillegal instruction运行硬件不支持 RVV 1.0确认 CPU 型号和实现版本6.4 测试结果波动大如果相同命令多次运行结果差异超过 5%优先检查# 查看当前 CPU 频率 cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq # 绑定到固定核心 taskset -c 4 ./saxpy_bench # 关闭频率调节为 performance cpupower frequency-set -g performance生产环境测试还要避免后台任务干扰。不建议在共享云主机上做微基准除非你能锁核并保证宿主机稳定。7. 最佳实践与工程建议7.1 编译参数统一如果是横向对比建议统一编译脚本。可以先在项目根目录放一个Makefile或build.sh#!/usr/bin/env bash set -e CCriscv64-unknown-linux-gnu-gcc CFLAGS-O3 -marchrv64gcv -stdc11 -fno-tree-loop-distribute-patterns $CC $CFLAGS bench/saxpy.c -o build/saxpy_bench $CC $CFLAGS bench/dotprod.c -o build/dotprod_bench注意-fno-tree-loop-distribute-patterns可以避免编译器把循环改成库函数调用减少测试干扰因素。7.2 内存对齐与分配RVV 的 load/store 在目标内存对齐时往往有更高效率。使用aligned_alloc时建议对齐到 64 字节或 128 字节兼容常见缓存行和向量寄存器宽度。float *x aligned_alloc(128, N * sizeof(float));7.3 合理选择 LMULLMUL 会影响寄存器占用、循环展开和溢出风险。在微基准中可以先跑 m1、m2、m4、m8 四组配置观察哪一组在当前处理器上最好。需要说明的是LMUL 越大不一定越快因为大 LMUL 会减少可以同时装载的数据块数量也可能增加寄存器压力。手写 intrinsic 时可以在循环内动态设置 LMUL但要避免频繁切换。更推荐的做法是为每个 kernel 分别编译在 Makefile 中通过不同-march参数指定。7.4 区分“RVV 能力强”和“整体性能强”很多 AI 芯片有独立的矩阵加速单元。RVV Benchmark 测出来的是通用核心向量能力而整体推理性能还可能取决于专用加速器、数据搬运和算子库的优化程度。写报告时要把这两类数据分开说明。7.5 自动化与报告化建议把 Benchmark 做成脚本方便回归编译所有微基准。绑核运行每个 kernel重复 5 次取中位数。自动计算 GFLOPS、带宽、效率。保存日志和 CSV 到独立目录。生成对比图表。这样可以快速发现编译器升级或内核优化带来的性能变化。7.6 关注功耗和温度在移动端或边缘设备上长时跑 RVV 负载会导致温度升高频率下降测试结果漂移。此时建议测试前等待芯片温度稳定。记录每轮跑分时的温度。使用短时运行模式避免热衰减影响。8. 结语RVV Benchmark 不是简单跑一个脚本然后看数字而是需要围绕硬件规格、编译策略、负载特征和测试环境做系统化设计。本文给出的方法基于 SiFive P870、Lanxin LX500、Epic Semi Contrail AIx 这类 RVV 1.0 处理器的通用评估需求从微基准代码到数据可视化都提供了一套可复制流程。下一步建议你根据实际拿到的硬件更换数据规模、LMUL 配置和编译选项收集自己平台的真实数据。重点关注三个方向自动向量化与手写 intrinsic 的差距、访存带宽对性能的限制、不同数据精度下的吞吐变化。如果测试中遇到其他问题欢迎在评论区补充交流。