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,代码自动适配,无需修改
标量循环 迭代1 迭代2 迭代3 迭代4 ... 每次处理1个元素 向量处理 单条向量指令处理16个元素 下一条向量指令继续 循环开销大幅减少

向量指令将循环迭代次数从n次降至约n/16次(VLEN=128时),同时vmacc单条指令完成乘加操作,显著降低指令总数与分支开销。