ARM intrinsics 指令集介绍 - Gather/Scatter

在 ARM SVE(Scalable Vector Extension) intrinsics 指令集中,svld1_gather_s32index_f32 和 svst1_scatter_s32index_f32 是用于非连续内存访问的向量加载 / 存储指令,分别实现 “聚集加载(gather)” 和 “散列存储(scatter)” 操作。它们通过 32 位整数索引(index)定位内存地址,适用于处理稀疏数据或非连续数组元素的场景(如机器学习中的嵌入层、稀疏矩阵运算等)。

1. svld1_gather_s32index_f32:聚集加载指令

功能

根据一组 32 位整数索引(相对于基地址的偏移量),从内存中非连续地加载单精度浮点数(float32) 到向量中,仅加载掩码(predicate)为1的元素位置。

语法

svfloat32_t svld1_gather_s32index_f32(
    svbool_t pg,               // 掩码:指示哪些元素需要加载
    const float32_t *base,     // 基地址:索引的参考起点
    svint32_t indices          // 32位整数索引向量:每个元素是相对于base的偏移量(单位:字节)
);

参数说明

  • pgsvbool_t 类型的掩码,只有掩码位为1的位置会执行加载操作,其他位置结果未定义(通常用0填充)。
  • base:内存基地址,所有索引均相对于该地址计算实际访问地址(实际地址 = base + indices[i])。
  • indices:SVE 向量,每个元素是 32 位整数,表示相对于base的字节偏移量(需注意内存对齐,避免未定义行为)。

返回值

svfloat32_t 类型的向量,其中掩码pg1的位置存储从base + indices[i]加载的float32值,其他位置无效(通常忽略)。

2. svst1_scatter_s32index_f32:散列存储指令

功能

将向量中的单精度浮点数(float32)非连续地存储到内存,存储位置由一组 32 位整数索引(相对于基地址的偏移量)指定,仅存储掩码(predicate)为1的元素。

语法

void svst1_scatter_s32index_f32(
    svbool_t pg,               // 掩码:指示哪些元素需要存储
    float32_t *base,           // 基地址:索引的参考起点
    svint32_t indices,         // 32位整数索引向量:每个元素是相对于base的偏移量(单位:字节)
    svfloat32_t data           // 待存储的单精度浮点数向量
);

参数说明

  • pgsvbool_t 类型的掩码,只有掩码位为1的位置会执行存储操作。
  • base:内存基地址,实际存储地址为base + indices[i]
  • indices:SVE 向量,每个元素是 32 位整数,表示相对于base的字节偏移量。
  • datasvfloat32_t 类型的向量,包含待存储的浮点数数据。

使用场景与示例

假设需要从一个稀疏数组中加载指定索引的元素并计算平均值,再将结果存储到另一个稀疏数组的指定位置。传统标量实现可能需要循环逐个访问,而 SVE 的 gather/scatter 指令可一次性处理多个元素:
#include <arm_sve.h>
#include <stdint.h>

// 从稀疏数组加载元素并计算平均值,再存储到目标稀疏数组
void sparse_operation(const float* src, const int32_t* src_indices, 
                      float* dst, const int32_t* dst_indices, 
                      int count) {
    int i = 0;
    // 生成初始掩码:i < count 时置位(适配SVE向量长度)
    svbool_t pg = svwhilelt_b32(i, count);
    
    while (svptest_any(svptrue_b32(), pg)) {
        // 1. 加载当前批次的索引(src_indices[i], src_indices[i+1], ...)
        svint32_t src_idx = svld1_s32(pg, src_indices + i);
        // 2. 聚集加载:从src[src_indices[i]]加载float32元素
        svfloat32_t src_data = svld1_gather_s32index_f32(pg, src, src_idx);
        
        // 3. 计算平均值(示例:简单累加后除以向量长度)
        float sum = svaddv_f32(pg, src_data);
        int num_elements = svcntw(); // 获取32位元素的向量长度
        float avg = sum / num_elements;
        svfloat32_t avg_vec = svdup_f32(avg); // 广播平均值到向量
        
        // 4. 加载目标索引(dst_indices[i], ...)
        svint32_t dst_idx = svld1_s32(pg, dst_indices + i);
        // 5. 散列存储:将平均值存储到dst[dst_indices[i]]
        svst1_scatter_s32index_f32(pg, dst, dst_idx, avg_vec);
        
        // 更新循环变量和掩码
        i += num_elements;
        pg = svwhilelt_b32(i, count);
    }
}

关键注意事项

  1. 内存安全:
    • 索引indices必须指向有效内存地址(base + indices[i] 不能越界),否则会导致未定义行为(如崩溃)。
    • 无需严格对齐(与普通svld1不同),但对齐访问可提升性能。
  2. 掩码作用:
    • 掩码pg决定了哪些元素参与加载 / 存储,适用于处理长度非向量倍数的场景(如count不是svcntw()的整数倍)。
  3. 性能考量:
    • Gather/Scatter 指令适用于非连续访问场景,但性能通常低于连续内存访问(如svld1/svst1),应避免在连续数据上滥用。
    • SVE 的向量长度可伸缩(如 128 位、256 位),指令会自动适配硬件,无需手动调整向量长度。

总结

  • svld1_gather_s32index_f32:通过 32 位索引从非连续内存位置加载 float32 数据到向量,适合稀疏数据读取。
  • svst1_scatter_s32index_f32:通过 32 位索引将向量中的 float32 数据存储到非连续内存位置,适合稀疏数据写入。

 

两者是处理非连续内存访问的核心指令,在稀疏计算、机器学习等场景中可显著提升效率,充分发挥 SVE 的向量处理能力。
posted @ 2025-08-13 12:44  lvmxh  阅读(62)  评论(0)    收藏  举报