为啥 vld4q_f32 与 4x vld1q_f32 不一样?

Posted

技术标签:

【中文标题】为啥 vld4q_f32 与 4x vld1q_f32 不一样?【英文标题】:Why is vld4q_f32 not the same as 4x vld1q_f32?为什么 vld4q_f32 与 4x vld1q_f32 不一样? 【发布时间】:2017-12-01 07:15:34 【问题描述】:
const float* H; //input data from elsewhere
const float32x4x4_t hCols = vld4q_f32(H);

const float32x4_t hCol0 = vld1q_f32(H + 0);
const float32x4_t hCol1 = vld1q_f32(H + 4);
const float32x4_t hCol2 = vld1q_f32(H + 8);
const float32x4_t hCol3 = vld1q_f32(H + 12);

我使用 hCol0、hCol1、hCol2、hCol3 的代码被替换为 hCols.val[0]、hCols.val[1]、hCols.val[2]、hCols.val[3] 但现在我变得非常奇怪的输出。

这两个负载不完全相同,vld4q_f32 可能更快吗?

【问题讨论】:

如果 Hfloat*,请记住 C 地址数学按操作数大小缩放,但 asm 没有。因此,您可能希望 H + 1H + 2 获得从 [reg + #4][reg + #8] 等加载的 asm。检查编译器输出以确保它看起来像您期望的那样通常很有帮助(可以捕获正确性和性能错误)。 @PeterCordes H + 4, 8, 12 由编译器正确处理。与众不同的是 vld4 指令本身。在AVX 中缺少这样令人愉快的说明真的让我感到震惊。 @Jake'Alquimista'LEE:哎呀,我没有意识到 vld4q 正在加载 4x4 = 16 个浮点数,而 vld1q 正在加载 4 个浮点数。我猜它正在加载 1(将广播加载或标量加载到零扩展向量 reg 中,例如 x86 的 movss xmm0, [mem])。当我一直在查看/思考我希望 asm 如何出现时,我有时肯定会混淆并用字节数偏移我的 C 指针,所以我的第一个猜测是这就是问题所在:P 【参考方案1】:

vld4 是一条与 vld1 非常不同的指令。

vld1 是一个直接加载,vld4 是一个扩展加载,其中每个值在四个寄存器中分配,一个元素接一个元素,一个寄存器接一个寄存器。

vld4q_f32(pSrc) will translate to:

vld4.32 d0, d2, d4, d6, [pSrc]!
vld4.32 d1, d3, d5, d7, [pSrc]

*pSrc:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E

================================================

And the values are loaded as following:
q0 (d0 + d1): 0, 4, 8, C
q1 (d2 + d3): 1, 5, 9, D
q2 (d4 + d5): 2, 6, A, E
q3 (d6 + d7): 3, 7, B, F

在汇编中,可以使用一条指令直接加载多个寄存器,例如:

vld1.32 q0, q1, [pSrc]!
vld1.32 q2, q3, [pSrc]

And the values are loaded as following:
q0 (d0 + d1): 0, 1, 2, 3
q1 (d2 + d3): 4, 5, 6, 7
q2 (d4 + d5): 8, 9, A, B
q3 (d6 + d7): C, D, E, F

但是,在内部函数中,您必须逐个注册: float32x4x4_t hCols;

hCols.val[0] = vld1q_f32(H + 0);
hCols.val[1] = vld1q_f32(H + 4);
hCols.val[2] = vld1q_f32(H + 8);
hCols.val[3] = vld1q_f32(H + 12);

android Studio 3.01 上,vld1q_f32_x4 是为此目的而定义的,但它似乎仍然存在错误。至少我没能成功构建。

顺便说一句,您是否要进行 4x4 矩阵乘法?恭喜,您刚刚在vld4 中找到了如何即时自动转置矩阵;

不过,不要在内在函数上浪费时间。 程序集版本的运行速度几乎是其三倍,而我对内部函数进行了 1:1 转换,由 Android Studio 3.01 附带的 Clang 编译。真的很烦。

使用内部函数纯粹是浪费时间,绝对是这样,至少在 ARM 上是这样。

【讨论】:

我很惊讶编译器在内部函数方面做得如此糟糕。在 x86 上,通常可以通过使用内在函数并仔细构建循环来获得所需的 asm 指令。 (但 x86 对指令调度不是很敏感;调度主要问题是否会损害 ARM 上的编译器?) 这么大的性能冲击,我什至都懒得检查拆机,我的血压已经够高了。对于 4x4 float32 矩阵乘法,您需要 12 个 128 位寄存器,aarch32 有 16 个,其中只需要保留 4 个。刚开始拆解看到vpush的那一刻,我的游戏就结束了。 是的,我正在尝试做 4x4 乘法。您有 arm asm 解决方案的链接吗? @DatChu 我可能会在下周在我的新博客上发布带有源代码的完整分析。

以上是关于为啥 vld4q_f32 与 4x vld1q_f32 不一样?的主要内容,如果未能解决你的问题,请参考以下文章

霓虹灯代码没有优化

带 Atmega32 的 4 位 4x20 LCD

手臂霓虹灯转置 4x4 uint32

为啥opengl中,“gl_Position”是vec4类型的?

Armv8a NEON 内联汇编代码:如何将 16x8 位向量转换为四个 4x32 位(整数)向量?

ARM Cortex A53上的NEON SIMD dotproduct速度不快