Page 81 - 《软件学报》2025年第9期
P. 81

3992                                                       软件学报  2025  年第  36  卷第  9  期


                 素个数, 以类型静态成员的形式对外提供调用接口. 编程人员可以使用 <VecType>::lane_type 和 <VecType>::
                 nlanes 获得向量类型对应的元数据.


                  a) 标量实现
                  void saxpy(size_t n, const float a, const float *x, float *y) {
                    for (size_t i = 0; i < n; ++i) {
                       y[i] = a * x[i] + y[i];                  // y = a * x + y 的标量计算
                    }                                           // 循环展开后具有向量优化机会
                  }
                  b)  使用 RISC-V Vector 内建函数的向量化实现
                  void saxpy(size_t n, const float a, const float *x, float *y) {
                    size_t vl = __riscv_vsetvlmax_e32m1();      // 设置状态寄存器并获取向量寄存器内最大元素个数
                    vfloat32m1_t vx, vy;                        // 声明 RVV 内建向量类型
                    for (size_t i = 0; i < n; i += vl) {        // 循环展开 vl 次,每轮迭代处理 vl 个元素
                       vx = __riscv_vle32_f32m1(x+i, vl);       // 向量加载,x
                       vy = __riscv_vle32_f32m1(y+i, vl);
                       vy = __riscv_vmacc(vy, a, vx, vl);       // 向量加载,y
                       __riscv_vse32(y+i, vy);                  // 向量乘加运算(RVV 允许向量与标量直接运算,无需广播)
                    }                                           // 向量存储,y
                  }
                  c)  使用 ARM NEON 内建函数的向量化实现
                  void saxpy(size_t n, const float a, const float *x, float *y) {
                    float32x4_t vx, vy;                         // 声明 ARM NEON 内建向量类型
                    float32x4_t va = vdupq_n_f32(a);            // 广播标量 a 到向量
                    for (int i = 0; i < n; i += 4) {            // 循环展开 4 次,每轮迭代处理 4 个元素
                       vx = vld1q_f32(x+i);                     // 向量加载,x
                       vy = vld1q_f32(y+i);                     // 向量加载,y
                       vy = vmlaq_s32(vy, va, vx));             // 向量乘加运算
                       vst1q_f32(y, vy);                        // 向量存储,y
                    }
                  }
                  d)  使用 x86 AVX2 内建函数的向量化实现
                  void saxpy(size_t n, const float a, const float *x, float *y) {
                    __m256 vx, vy;                              // 声明 x86 AVX2 内建向量类型
                    __m256 va = _mm256_set1_ps(a);              // 广播标量 a 到向量
                    for (int i = 0; i < n; i += 8) {            // 循环展开 8 次,每轮迭代处理 8 个元素
                       vx = _mm256_loadu_ps(x+i);               // 向量加载,x
                       vy = _mm256_loadu_ps(y+i);               // 向量加载,y
                       vy = _mm256_add_ps(vy, _mm256_mul_ps(va, vx));   // 向量乘加运算
                       _mm256_storeu_ps(y, vy);                 // 向量存储,y
                    }
                  }
                  e)  使用通用内建函数的向量化实现
                  void saxpy(size_t n, const float a, const float *x, float *y) {
                    v_float32 vx, vy;                           // 声明通用内建向量类型
                    v_float32 va = v_setall_f32(a);             // 广播标量 a 到向量
                    size_t N = v_float32::nlanes;               // 调用接口获取通用类型包含的元素个数
                    for (int i = 0; i < n; i += N) {            // 循环展开 N 次,每轮迭代处理 N 个元素
                       vx = v_load(x+i);                        // 向量加载,x
                       vy = v_load(y+i);                        // 向量加载,y
                       vy = v_add(vy, v_mul(vx, vy));           // 向量乘加运算
                       v_store(y, vy);                          // 向量存储,y
                    }
                  }
                                   图 2 不同平台的     saxpy  算法向量化实现对比 (不考虑尾循环处理)


                 3.1.2    向量操作
                    OpenCV  的通用内建函数根据目标硬件平台所提供的向量计算能力和计算机视觉领域的计算特征, 提供了                                7
                 类向量操作的抽象, 以函数调用形式对外提供接口.
   76   77   78   79   80   81   82   83   84   85   86