Page 83 - 《软件学报》2025年第9期
P. 83
3994 软件学报 2025 年第 36 卷第 9 期
硬件抽象层
通用内建函数
向量类型 向量操作
v_float32 v_fma(…)
目标平台检查
cpu_avx.cpp cpu_neon.cpp cpu_rvv.cpp
x86 AVX 实现 ARM Neon 实现 RISC-V Vector 实现
CV_AVX CV_NEON CV_RVV
CV_SIMD256 CV_SIMD128 CV_SIMD128
_mm256 float32x4_t vfloat32m1_t
_mm256_fmadd_ps(…) vfmaq_f32(…) __riscv_vfmacc(…)
图 3 通用内建函数到平台特定内建函数的映射过程示意图
更为棘手的是, RISC-V 向量扩展的向量寄存器长度在编译时是未知的, 存储其向量类型所需的空间也就无法
确定, 这种类型被称为无大小 (sizeless) 类型. 此种类型在实际使用中通常受到一定限制: 由于实例化对象或确定
栈帧布局过程中, 编译器需要准确计算出类的大小和类成员的偏移量, 而 RISC-V 向量扩展的内建类型大小无法
在编译时确定. 因此, 类或结构体内不能声明这些类型的成员变量, 这与 OpenCV 硬件抽象层的向量类型封装机制
不兼容. OpenCV 目前采用的替代方案是在类型封装中使用内存中的数组来存放数据, 同时增加 RISC-V 向量类型
和通用内建函数向量类型之间的转换函数, 一定程度上解决了 RISC-V 向量类型与包装类型不兼容的问题. 但两
种类型之间的转换不可避免地使用访存指令实现数组和寄存器的数据交换, 因而产生了冗余的访存指令: 在使用
通用内建函数时, 每个 RISC-V 向量类型 (数据在向量寄存器中) 到通用向量类型 (数据在内存数组中) 都需要执行
一次显式类型转换, 进而产生一条冗余的内存写入指令; 该对象需要再作为向量类型被使用时, 又会产生一次隐式
的由通用向量类型到 RISC-V 向量类型的类型转换, 进而产生一条冗余的内存读取指令; 此外, 如果在使用时将通
用向量类型的对象进行了赋值, 还会额外产生内存数组的拷贝操作. 由于访存指令的开销通常较大, 这些冗余操作
极大影响了执行性能, 甚至抵消了向量优化带来的性能提升, 以至于通用内建函数中固定长度版本的 RISC-V 向
量扩展实现的性能表现还低于未经任何向量优化的标量版本, 不具备应用价值.
上述问题均由 RISC-V 向量扩展中向量寄存器可变长的特性而引发, 可以预见同样具有可变寄存器长度特性
的 ARM SVE/SVE2 扩展也将遇到类似问题. 究其根本, 是由传统定长 SIMD 扩展发展演化而来的 OpenCV 硬件抽
象层设计无法适应新兴的可变长体系结构导致的.
3.3 面向 RISC-V 向量扩展的通用内建函数优化实现
将 RISC-V 向量扩展视作固定长度 SIMD 扩展将造成寄存器资源浪费, 并产生冗余访存指令, 没有产生应有
的向量优化效果. 因此, 本文使用所提出的面向 RISC-V 向量扩展的硬件抽象层设计方法, 优化了 OpenCV 的通用
内建函数的设计与实现. 新的通用内建函数可以兼容 RISC-V 向量扩展的可变长特性, 并使用 RISC-V 向量扩展的
平台内建函数实现了其编程接口, 从而使得通用内建函数所编写的优化算法能够在拥有 RISC-V 向量扩展的硬件
设备上获得性能提升.
与 OpenCV 现有定长的通用内建函数设计方案相比, 本文方法的主要差异如图 4 所示. 首先, 在通用内建函数

