1. 向量加法内核
任务目标
在RISC-V向量扩展(RVV)环境下实现向量加法内核,将两个长度为N的浮点数组逐元素相加,并与等价的标量实现进行性能对比。通过本案例掌握RVV intrinsics的基本使用方法,并建立可复现的性能测量流程。
环境与依赖
- 支持RVV的编译器:GCC 12+(
-march=rv64gcv)或Clang 15+ - 模拟器/开发板:QEMU 7.0+(
qemu-riscv64)或含RVV的实机(如SiFive U74) - 头文件:
<riscv_vector.h>(编译器自带)
核心实现
// vec_add.c
#include <riscv_vector.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 4096
#define ITER 1000
// 标量版本
void add_scalar(const float *a, const float *b, float *c, int n) {
for (int i = 0; i < n; i++)
c[i] = a[i] + b[i];
}
// RVV向量版本(使用intrinsic)
void add_vector(const float *a, const float *b, float *c, int n) {
size_t vl;
for (size_t i = 0; i < n; i += vl) {
vl = vsetvl_e32m8(n - i); // 每次处理尽可能多的元素
vfloat32m8_t va = vle32_v_f32m8(a + i, vl);
vfloat32m8_t vb = vle32_v_f32m8(b + i, vl);
vfloat32m8_t vc = vfadd_vv_f32m8(va, vb, vl);
vse32_v_f32m8(c + i, vc, vl);
}
}
// 性能测试辅助
double time_ms(void (*func)(const float*, const float*, float*, int),
const float *a, const float *b, float *c) {
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
for (int i = 0; i < ITER; i++)
func(a, b, c, N);
clock_gettime(CLOCK_MONOTONIC, &t1);
return (t1.tv_sec - t0.tv_sec) * 1000.0 +
(t1.tv_nsec - t0.tv_nsec) / 1e6;
}
int main() {
float *a = aligned_alloc(64, N * sizeof(float));
float *b = aligned_alloc(64, N * sizeof(float));
float *c = aligned_alloc(64, N * sizeof(float));
float *c_ref = aligned_alloc(64, N * sizeof(float));
for (int i = 0; i < N; i++) {
a[i] = i * 0.5f;
b[i] = i * 0.25f;
}
// 正确性验证
add_vector(a, b, c, N);
add_scalar(a, b, c_ref, N);
int ok = 1;
for (int i = 0; i < N; i++) {
if (c[i] != c_ref[i]) { ok = 0; break; }
}
printf("Correctness: %s\n", ok ? "PASS" : "FAIL");
// 性能对比
double t_scalar = time_ms(add_scalar, a, b, c);
double t_vector = time_ms(add_vector, a, b, c);
printf("Scalar: %.3f ms\n", t_scalar);
printf("Vector: %.3f ms\n", t_vector);
printf("Speedup: %.2fx\n", t_scalar / t_vector);
free(a); free(b); free(c); free(c_ref);
return 0;
}
编译与运行
# 使用QEMU模拟(需先安装qemu-riscv64)
riscv64-linux-gnu-gcc -O3 -march=rv64gcv -mabi=lp64d -static \
vec_add.c -o vec_add -lm
qemu-riscv64 ./vec_add
# 若在RVV实机上运行,直接编译执行:
# gcc -O3 -march=rv64gcv -mabi=lp64d vec_add.c -o vec_add -lm
# ./vec_add
验证方法与判定标准
- 正确性:程序输出
Correctness: PASS,表示向量与标量结果完全一致(浮点加法满足结合律,此处N较小且数值简单,可精确比对)。 - 性能对比:观察
Speedup值。在QEMU中由于模拟开销,加速比可能接近1或小于1,这属于正常现象;在真实RVV硬件上,预期加速比大于1(通常2~8倍,取决于硬件VLEN和编译器优化质量)。 - 判定通过:正确性PASS且程序能完整跑完即为功能通过;性能数据仅作记录,不设硬性阈值。
关键说明
vsetvl_e32m8请求使用m8(8个向量寄存器组)配置,每次循环处理尽可能多的元素;vl为实际处理的元素数,自动处理尾部不足一个寄存器的元素。- 使用
aligned_alloc(64, ...)确保数据对齐,避免跨页边界导致的性能损失。 - 若编译器不支持
-march=rv64gcv,可尝试-march=rv64gcv_zvl128b指定最小向量长度。 - 本案例仅演示向量加法内核本身,不涉及内存带宽优化(如数据预取)或多核并行,这些属于后续案例内容。