我有一个程序,需要对8x8 float 32矩阵运行转置操作很多次。我想使用 neon SIMD内部函数转置这些矩阵。我知道数组总是包含8x8浮点元素。我有一个基线非内部解决方案如下:
void transpose(float *matrix, float *matrixT) {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
matrixT[i*8+j] = matrix[j*8+i];
}
}
}
我还创建了一个内在解,它可以转置8x8矩阵的每个4x 4象限,并交换第二象限和第三象限的位置。
void transpose_4x4(float *matrix, float *matrixT, int store_index) {
float32x4_t r0, r1, r2, r3, c0, c1, c2, c3;
r0 = vld1q_f32(matrix);
r1 = vld1q_f32(matrix + 8);
r2 = vld1q_f32(matrix + 16);
r3 = vld1q_f32(matrix + 24);
c0 = vzip1q_f32(r0, r1);
c1 = vzip2q_f32(r0, r1);
c2 = vzip1q_f32(r2, r3);
c3 = vzip2q_f32(r2, r3);
r0 = vcombine_f32(vget_low_f32(c0), vget_low_f32(c2));
r1 = vcombine_f32(vget_high_f32(c0), vget_high_f32(c2));
r2 = vcombine_f32(vget_low_f32(c1), vget_low_f32(c3));
r3 = vcombine_f32(vget_high_f32(c1), vget_high_f32(c3));
vst1q_f32(matrixT + store_index, r0);
vst1q_f32(matrixT + store_index + 8, r1);
vst1q_f32(matrixT + store_index + 16, r2);
vst1q_f32(matrixT + store_index + 24, r3);
}
void transpose(float *matrix, float *matrixT) {
// Transpose top-left 4x4 quadrant and store the result in the top-left 4x4 quadrant
transpose_4x4(matrix, matrixT, 0);
// Transpose top-right 4x4 quadrant and store the result in the bottom-left 4x4 quadrant
transpose_4x4(matrix + 4, matrixT, 32);
// Transpose bottom-left 4x4 quadrant and store the result in the top-right 4x4 quadrant
transpose_4x4(matrix + 32, matrixT, 4);
// Transpose bottom-right 4x4 quadrant and store the result in the bottom-right 4x4 quadrant
transpose_4x4(matrix + 36, matrixT, 36);
}
然而,这个解决方案比基准非内在解决方案的性能要慢。我正在努力寻找一个更快的解决方案,如果有的话,可以转置我的8x8矩阵。任何帮助都将不胜感激!
编辑:两个解决方案都使用-O 1标志编译。
2条答案
按热度按时间tkclm6bt1#
首先,您不应该期望从以下方面开始就能获得巨大的性能提升:
总而言之,通过矢量化只节省了一点点带宽-仅此而已
至于4x 4转置,您甚至不需要单独的函数,只需一个宏即可:
将完成这项工作,因为当您使用
vld4
加载数据时, neon 会动态执行4x 4转置。但此时你应该问问自己,如果4x 4转置几乎不花费任何成本,那么你的方法--在实际计算之前转置所有矩阵--是否是正确的。这一步最终可能纯粹是浪费计算和带宽。优化不应该局限于最后一步,而应该从设计阶段就开始考虑。
8x8转座是一种不同的动物,虽然:
它可以归结为:16加载+ 32事务+ 16存储与64加载+ 64存储
现在我们可以清楚地看到这真的不值得。上面的 neon 灯例程可能会快一点,但我怀疑它最终会有什么不同。
不,你不能再优化它了。没有人能。只要确保指针是64字节对齐的,测试一下,然后自己决定。
上面是手工优化的汇编版本,它很可能更短(尽可能短),但并不完全有意义地快于:
下面是我想要的纯C版本:
或
PS:如果你声明
pDst
和pSrc
uint32_t *
,可能会带来一些性能/功耗上的增益,因为编译器肯定会生成纯整数机器码,它有各种各样的寻址模式,并且只使用w
寄存器而不是s
寄存器。PS2:Clang已经使用了
w
寄存器而不是s
寄存器,而GCC正在被GCC...... GNU-shills什么时候才能最终承认GCC对ARM来说是一个非常糟糕的选择?godbolt
PS3:下面是汇编中的非霓虹灯版本(零延迟),因为我对上面的Clang和GCC都非常失望(甚至震惊):
如果你仍然坚持做纯8x8转置,这可能是你能得到的最好的版本,它可能比 neon 汇编版本慢一点,但消耗的能量要少得多。
8ehkhllq2#
可以优化在另一个答案中呈现的8x8 neon 代码; 8x8转置不仅可以被认为是
[A B;C D]' == [A' C'; B' D']
的递归版本,而且可以被认为是zip或unzip的重复应用。对于8x8矩阵,我们需要应用此算法3次,并通过vld 4阅读数据,其中两次已经完成。
应该也能够通过从
vld1q_f32_x4
开始,然后uzpq
并以vst4q_f32
结束来执行转置。