• NEON优化:关于交叉存取与反向交叉存取


    NEON优化:关于交叉存取与反向交叉存取

    背景


    NEON优化过程中,经常遇到内存读写,寄存器变量间的读写,NEON内存读写指令中默认是交叉存取,部分特殊指令可以反向交叉存取。

    什么是交叉存取,什么是反向交叉存取?

    • 交叉读写:ld2q/3q/4q, st2q/3q/4q, zip
      • 说明:按间隔(数目为2q/3q/4q中的数字)录入到相应寄存器
      • 举例:如内存存储的连续数据为a1 b1 a2 b2,ld2q读出到2个寄存器为:val[0]: a1 a2, val[1]: b1 b2
    • 反向交叉读写:ld1q/st1q, uzp
      • 说明:按内存方向连续依次录入到寄存器
      • 举例:如内存存储的连续数据为a1 b1 a2 b2,ld1q读出到1个寄存器为:val[0]: a1 b1 a2 b2

    相关函数


    主要从内存读写、寄存器间交互进行说明。

    • 内存与寄存器交互
      • ld1q/st1q
        • 仅1维交叉读写与正常读写功能一致
      • ld2q/st2q及3q、4q
        • 作用:均为交叉读写,目的是为了处理不同声道、维度的信息
        • 说明:纵向读数据,横向写数据(按列读,按行写)
        • 注意:ld4q/st4q成对使用时,可还原回去,相当于对矩阵转置后放到寄存器中,再转置后放回到内存
    • 寄存器间交互
      • vzip交叉存取
        • 指令:int32x4x2_t vzipq_s32(int32x4_t a, int32x4_t b);
        • 释义:
          • 输入:a = {0 1 2 3},b = {4 5 6 7}
          • 输出:val[0]:0 4 1 5,val[1]:2 6 3 7
          • 说明:纵向读数据,横向写数据
          • 具体:先读a一个数据,再读b一个数据,成04152637,横向写完val0所在4个值后(0415),再写入val1剩余4个值(2637)
      • uzpq反向交叉存取
        • 指令:int32x4x2_t vuzpq_s32(int32x4_t a, int32x4_t b);
        • 释义:类似于解交织声道数据
          • 输入:a = {0 1 2 3},b = {4 5 6 7}
          • 输出:val[0]:0 2 4 6,val[1]:1 3 5 7
          • 说明:横向读数据,纵向写数据
          • 具体:a和b的值顺序读出,成01234567,将其val[0]/val[1]按列写入进去。

    测试代码


    ld4q/st4q功能测试

    #define ROW_NUM   4
    #define COL_NUM   4
    
    // initial
    float M[ROW_NUM][COL_NUM] = {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11},
        {12, 13, 14, 15},
    };
    
    int i, j;
    float32x4x4_t vf32x4x4fTmpABCD = vld4q_f32(&M[0][0]);
    float MT[4][4];
    vst1q_f32(&MT[0][0], vf32x4x4fTmpABCD.val[0]); // 0 4 8 12
    vst1q_f32(&MT[1][0], vf32x4x4fTmpABCD.val[1]);
    vst1q_f32(&MT[2][0], vf32x4x4fTmpABCD.val[2]);
    vst1q_f32(&MT[3][0], vf32x4x4fTmpABCD.val[3]); // 3 7 11 15
    printf("ver1:\n");
    for (i = 0; i < ROW_NUM; i++) {
        for (j = 0; j < COL_NUM; j++) {
            printf("%f ", MT[i][j]);
            MT[i][j] = 0.;
        }
        printf("\n");
    }
    
    vst4q_f32(&MT[0][0], vf32x4x4fTmpABCD);
    printf("ver2:\n");
    for (i = 0; i < ROW_NUM; i++) {
        for (j = 0; j < COL_NUM; j++) {
            printf("%f ", MT[i][j]);
            MT[i][j] = 0.;
        }
        printf("\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    输出结果

    ver1:
    0.000000 4.000000 8.000000 12.000000
    1.000000 5.000000 9.000000 13.000000
    2.000000 6.000000 10.000000 14.000000
    3.000000 7.000000 11.000000 15.000000
    ver2:
    0.000000 1.000000 2.000000 3.000000
    4.000000 5.000000 6.000000 7.000000
    8.000000 9.000000 10.000000 11.000000
    12.000000 13.000000 14.000000 15.000000

    zip/uzp功能测试

    #define ROW_NUM   4
    #define COL_NUM   4
    
    // initial
    float M[ROW_NUM][COL_NUM] = {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11},
        {12, 13, 14, 15},
    };
    
    float MT[4][4];
    
    // 按列读,按行写
    float32x4_t vf32x4fTmp1 = vld1q_f32(&M[0][0]); // 0 1 2 3
    float32x4_t vf32x4fTmp2 = vld1q_f32(&M[1][0]); // 4 5 6 7
    float32x4x2_t vf32x4x2fTmpZip = vzipq_f32(vf32x4fTmp1, vf32x4fTmp2);
    vst1q_f32(&MT[0][0], vf32x4x2fTmpZip.val[0]); // 0 4 1 5
    vst1q_f32(&MT[1][0], vf32x4x2fTmpZip.val[1]); // 2 6 3 7
    
    // 按行读,按列写
    float32x4_t vf32x4fTmp3 = vld1q_f32(&M[2][0]); // 8 9 10 11
    float32x4_t vf32x4fTmp4 = vld1q_f32(&M[3][0]); // 12 13 14 15
    float32x4x2_t vf32x4x2fTmpUzp = vuzpq_f32(vf32x4fTmp3, vf32x4fTmp4);
    vst1q_f32(&MT[2][0], vf32x4x2fTmpUzp.val[0]); // 8 10 12 14
    vst1q_f32(&MT[3][0], vf32x4x2fTmpUzp.val[1]); // 9 11 13 15
    
    printf("ver1:\n");
    int i, j;
    for (i = 0; i < ROW_NUM; i++) {
        for (j = 0; j < COL_NUM; j++) {
            printf("%f ", MT[i][j]);
            MT[i][j] = 0.;
        }
        printf("\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    小结


    有了前面这些对比,所谓交叉存取、反向交叉存取,简单来看,就是想象一个矩阵,交叉存取就是按列读出来,按行写入到新变量,反向交叉存取则按行读出来,按列写进去,如此而已。

  • 相关阅读:
    Python学习笔记七之文件操作:打开与写入、创建与删除、遍历文件夹批处理等
    钟汉良日记:我怎么做到一天副业收款520的?
    vue3+TS实现简易组件库
    UVA 1152 和为 0 的 4 个值 4 Values whose Sum is 0
    JS 防抖封装方法
    Java项目硅谷课堂学习笔记-P7点播模块管理-后台-管理员端
    扬帆际海:shopee跨境电商客服回复流程
    Java版本+企业电子招投标系统源代码之电子招投标系统建设的重点和未来趋势
    javascript将html中的dom元素转图片
    Win10系统使用pip安装juypter notebook过程记录(安装在系统盘以外的盘)
  • 原文地址:https://blog.csdn.net/qq_17256689/article/details/125562882