我是 RVV 的新手,正在使用 RVV1.0 重写一些汇编函数。现在我遇到了一些关于矩阵转置的问题。 Arm NEON 提供 vtrn 指令,帮助进行转置:
# element wide = 8
.macro TRANSPOSE4x4 r0 r1 r2 r3
vtrn.16 \r0, \r2
vtrn.16 \r1, \r3
vtrn.8 \r0, \r1
vtrn.8 \r2, \r3
.endm
# element wide = 16
.macro TRANSPOSE4x4_16 d0 d1 d2 d3
vtrn.32 \d0, \d2
vtrn.32 \d1, \d3
vtrn.16 \d0, \d1
vtrn.16 \d2, \d3
.endm
# element wide = 8
.macro TRANSPOSE8x8 r0 r1 r2 r3 r4 r5 r6 r7
vtrn.32 \r0, \r4
vtrn.32 \r1, \r5
vtrn.32 \r2, \r6
vtrn.32 \r3, \r7
vtrn.16 \r0, \r2
vtrn.16 \r1, \r3
vtrn.16 \r4, \r6
vtrn.16 \r5, \r7
vtrn.8 \r0, \r1
vtrn.8 \r2, \r3
vtrn.8 \r4, \r5
vtrn.8 \r6, \r7
.endm
但是在RVV中,我没有找到像vtrn这样的指令,为什么RVV不提供这个?如果我有一个4*4的矩阵,每一行分别存储在v1,v2,v3,v4中,并且SEW=16,就像
TRANSPOSE4x4_16
在arm NEON中,如何转置它?
我尝试使用这个博客中的一些方法,但失败了。
我正在重写一个名为x264的视频编解码器库,它有一个名为sub4x4_dct的汇编函数:
.macro SUMSUB_AB sum, diff, a, b
vadd.s16 \sum, \a, \b
vsub.s16 \diff, \a, \b
.endm
.macro DCT_1D d0 d1 d2 d3 d4 d5 d6 d7
SUMSUB_AB \d1, \d6, \d5, \d6
SUMSUB_AB \d3, \d7, \d4, \d7
vadd.s16 \d0, \d3, \d1
vadd.s16 \d4, \d7, \d7
vadd.s16 \d5, \d6, \d6
vsub.s16 \d2, \d3, \d1
vadd.s16 \d1, \d4, \d6
vsub.s16 \d3, \d7, \d5
.endm
function sub4x4_dct_neon
mov r3, #FENC_STRIDE
mov ip, #FDEC_STRIDE
vld1.32 {d0[]}, [r1,:32], r3
vld1.32 {d1[]}, [r2,:32], ip
vld1.32 {d2[]}, [r1,:32], r3
vsubl.u8 q8, d0, d1
vld1.32 {d3[]}, [r2,:32], ip
vld1.32 {d4[]}, [r1,:32], r3
vsubl.u8 q9, d2, d3
vld1.32 {d5[]}, [r2,:32], ip
vld1.32 {d6[]}, [r1,:32], r3
vsubl.u8 q10, d4, d5
vld1.32 {d7[]}, [r2,:32], ip
vsubl.u8 q11, d6, d7
DCT_1D d0, d1, d2, d3, d16, d18, d20, d22
TRANSPOSE4x4_16 d0, d1, d2, d3
DCT_1D d4, d5, d6, d7, d0, d1, d2, d3
vst1.64 {d4-d7}, [r0,:128]
bx lr
endfunc
C 实现是:
// dctcoef : int16_t
// pixel: uint8_t
static inline void pixel_sub_wxh( dctcoef *diff, int i_size,
pixel *pix1, int i_pix1, pixel *pix2, int i_pix2 )
{
for( int y = 0; y < i_size; y++ )
{
for( int x = 0; x < i_size; x++ )
diff[x + y*i_size] = pix1[x] - pix2[x];
pix1 += i_pix1;
pix2 += i_pix2;
}
}
static void sub4x4_dct( dctcoef dct[16], pixel *pix1, pixel *pix2 )
{
dctcoef d[16];
dctcoef tmp[16];
pixel_sub_wxh( d, 4, pix1, FENC_STRIDE, pix2, FDEC_STRIDE );
for( int i = 0; i < 4; i++ )
{
int s03 = d[i*4+0] + d[i*4+3];
int s12 = d[i*4+1] + d[i*4+2];
int d03 = d[i*4+0] - d[i*4+3];
int d12 = d[i*4+1] - d[i*4+2];
tmp[0*4+i] = s03 + s12;
tmp[1*4+i] = 2*d03 + d12;
tmp[2*4+i] = s03 - s12;
tmp[3*4+i] = d03 - 2*d12;
}
for( int i = 0; i < 4; i++ )
{
int s03 = tmp[i*4+0] + tmp[i*4+3];
int s12 = tmp[i*4+1] + tmp[i*4+2];
int d03 = tmp[i*4+0] - tmp[i*4+3];
int d12 = tmp[i*4+1] - tmp[i*4+2];
dct[i*4+0] = s03 + s12;
dct[i*4+1] = 2*d03 + d12;
dct[i*4+2] = s03 - s12;
dct[i*4+3] = d03 - 2*d12;
}
}
其实我并没有弄清楚这个算法在汇编中是如何实现的,所以打算直接模仿NEON的汇编方法来写。这是我已经实现的:
#define FENC_STRIDE 16
#define FDEC_STRIDE 32
.macro SUMSUB_AB sum, diff, a, b
vadd.vv \sum, \a, \b
vsub.vv \diff, \a, \b
.endm
.macro DCT_1D d0 d1 d2 d3 d4 d5 d6 d7
SUMSUB_AB \d1, \d6, \d5, \d6
SUMSUB_AB \d3, \d7, \d4, \d7
vadd.vv \d0, \d3, \d1
vadd.vv \d4, \d7, \d7
vadd.vv \d5, \d6, \d6
vsub.vv \d2, \d3, \d1
vadd.vv \d1, \d4, \d6
vsub.vv \d3, \d7, \d5
.endm
function sub4x4_dct_rvv
vsetivli t0, 4, e8, mf2, ta, ma
vle8.v v24, (a1)
add a1, a1, FENC_STRIDE
vle8.v v25, (a2)
add a2, a2, FDEC_STRIDE
vle8.v v26, (a1)
add a1, a1, FENC_STRIDE
vle8.v v27, (a2)
add a2, a2, FDEC_STRIDE
vle8.v v5, (a1)
add a1, a1, FENC_STRIDE
vle8.v v6, (a2)
add a2, a2, FDEC_STRIDE
vle8.v v7, (a1)
vle8.v v8, (a2)
vwsubu.vv v9, v24, v25
vwsubu.vv v10, v26, v27
vwsubu.vv v11, v27, v5
vwsubu.vv v30, v7, v8
DCT_1D v24, v25, v26, v27, v9, v10, v11, v30
# TRANSPOSE4x4_16 v24, v25, v26, v27
lui t1, 11
addi t1, t1, -1366
vsetivli zero, 4, e16, m1, tu, mu
vmv.s.x v0, t1
lui t1, 5
addi t1, t1, 1365
vmv.s.x v12, t1
vmv1r.v v15, v24
vslideup.vi v15, v25, 1, v0.t
vmv1r.v v14, v26
vslideup.vi v14, v27, 1, v0.t
vmv1r.v v0, v12
vslidedown.vi v25, v24, 1, v0.t
vslidedown.vi v27, v26, 1, v0.t
vmv1r.v v12, v15
vslideup.vi v12, v14, 2
vmv1r.v v13, v25
vslideup.vi v13, v27, 2
vsetivli zero, 2, e16, m1, tu, ma
vslidedown.vi v14, v15, 2
vslidedown.vi v27, v25, 2
vmv1r.v v15, v27
vmv1r.v v24, v12
DCT_1D v5, v6, v7, v8, v24, v25, v26, v27
li t1, 8
vlsseg4e16.v v5, (a0), t1
ret
endfunc
我终于得到了错误的答案。而且看起来无论输入如何,输出都保持不变: 我发现我对手臂寄存器有一些误解,我正在修复它。
关于转置大小,4x4和8x8就可以了。
我使用 vrgather_vv、vslideup_vx 和 vmerge_vvm 指令实现了 4x4 矩阵转置。然而,表现却不尽如人意!看来向量寄存器之间的数据交换成本相当高。