2. 向量点积
任务目标
实现一个基于RISC-V向量扩展(RVV)的向量点积函数,通过向量指令替代标量循环,减少指令开销并提升计算效率。本案例以int32_t类型、固定长度向量为例,完成核心计算逻辑并给出可运行的验证路径。
环境与依赖
- 支持RVV的编译器:GCC 12+(
-march=rv64gcv)或Clang 14+ - 模拟器/开发板:QEMU(
qemu-riscv64)或含向量扩展的RISC-V开发板 - 无需额外库,仅使用标准头文件
核心实现
#include <riscv_vector.h>
#include <stdint.h>
#include <stddef.h>
// 向量点积:计算 a[0..n-1] 与 b[0..n-1] 的内积
// 要求:a、b 均按 16 字节对齐(VLEN=128 时),n 为任意正整数
int32_t vector_dot(const int32_t *a, const int32_t *b, size_t n) {
vint32m4_t vsum = vmv_v_x_i32m4(0, 4); // 初始化累加向量为0,m4=4个寄存器组
size_t vl;
// 每次处理 4*VLEN/32 个元素(VLEN=128时每次16个)
for (; n > 0; n -= vl) {
vl = vsetvl_e32m4(n); // 根据剩余元素数设置vl
vint32m4_t va = vle32_v_i32m4(a, vl);
vint32m4_t vb = vle32_v_i32m4(b, vl);
vsum = vmacc_vv_i32m4(vsum, va, vb, vl); // vsum += va * vb
a += vl;
b += vl;
}
// 将向量寄存器组归约为标量
vint32m1_t vred = vredsum_vs_i32m4_i32m1(vsum, vmv_v_x_i32m1(0, 1), 4);
return vmv_x_s_i32m1_i32(vred);
}
编译与运行
# 编译(QEMU用户态模拟)
riscv64-unknown-elf-gcc -march=rv64gcv -mabi=lp64d -O2 -o dot_test dot_test.c
# 或使用QEMU直接运行
qemu-riscv64 ./dot_test
验证方法
编写包含主函数的测试文件,对比向量实现与标量实现的结果:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 标量参考实现
int32_t scalar_dot(const int32_t *a, const int32_t *b, size_t n) {
int32_t sum = 0;
for (size_t i = 0; i < n; i++) sum += a[i] * b[i];
return sum;
}
int main() {
const size_t N = 1000;
int32_t *a = aligned_alloc(16, N * sizeof(int32_t));
int32_t *b = aligned_alloc(16, N * sizeof(int32_t));
for (size_t i = 0; i < N; i++) { a[i] = rand() % 100; b[i] = rand() % 100; }
int32_t r_vec = vector_dot(a, b, N);
int32_t r_scl = scalar_dot(a, b, N);
printf("向量结果: %d\n标量结果: %d\n", r_vec, r_scl);
printf("验证: %s\n", (r_vec == r_scl) ? "通过" : "失败");
free(a); free(b);
return 0;
}
通过条件:程序输出验证: 通过,且向量与标量结果一致。可修改N为不同值(如1、7、100、4095)测试边界情况。
关键说明
vsetvl_e32m4动态调整向量长度,使代码支持任意n,无需填充到倍数- 使用
m4寄存器组(4个向量寄存器组合并)提升单次处理量,VLEN=128时每次处理16个32位整数 vmacc执行乘累加,避免单独的乘法和加法指令- 若目标平台VLEN≠128,代码自动适配,无需修改
向量指令将循环迭代次数从n次降至约n/16次(VLEN=128时),同时vmacc单条指令完成乘加操作,显著降低指令总数与分支开销。