为啥 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 可能更快吗?
【问题讨论】:
如果H
是 float*
,请记住 C 地址数学按操作数大小缩放,但 asm 没有。因此,您可能希望 H + 1
、H + 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 不一样?的主要内容,如果未能解决你的问题,请参考以下文章
为啥opengl中,“gl_Position”是vec4类型的?