如何使用 ARM Neon 内在函数对 IF 块进行矢量化?
Posted
技术标签:
【中文标题】如何使用 ARM Neon 内在函数对 IF 块进行矢量化?【英文标题】:How can I vectorize an IF block using ARM Neon intrinsics? 【发布时间】:2017-11-16 01:26:18 【问题描述】:我想在 ARM 处理器上处理大量浮点数, 使用 Neon 技术一次计算四个。像加法和乘法这样的操作一切都很好,但是如果我的计算进入一个 IF 块我该怎么办?示例:
// In the non-vectorized original code, A is an array of many floating-point
// numbers, which are calculated one at a time. Now they're packed
// into a vector and processed four at a time
...calculate A...
if (A > 10.f)
A = A+5.f;
else
A = A+10.f;
现在,我应该执行哪个 IF 分支?如果正在处理的向量中的某些值大于 10 而某些值小于 10 怎么办?甚至可以像这样矢量化代码吗?
【问题讨论】:
见:Is the preference with SIMD to always get rid of branching? 见:How to use NEON comparison (greater than or equal to) instruction? 见:Neon Comparison 【参考方案1】:到目前为止,我将通过描述如何在 Neon 内在函数中对其进行编码来补充答案。
通常,您不会根据并行寄存器内容执行 IF 块逻辑,因为一个值可能需要 IF 块的一个分支,而同一寄存器中的不同值可能需要另一个。 “急切执行”意味着首先进行所有可能的计算,然后决定在哪些通道中实际使用哪些结果。 (请记住,仅对寄存器的一个通道进行 Neon 计算不会获得任何收益。任何必须完成的计算都将在所有 2 或 4 个通道上完成。)
要进行基于 IF 的计算,请使用 Neon 条件内在函数,例如“大于”做一个位掩码,然后一个“选择”函数根据位掩码填充最终结果
双 aval[2] = 11.5, 9.5;
float64x2_t AA= vld1q_f64(aval); // an array with two 64-bit double values
float64x2 TEN= vmovq_n_f64(10.f); // load a constant into a different array
float64x2 FIVE= vmovq_n_f64(5.f); // load a constant into a different array
// Do both of the computations
float64x2 VALIFTRUE = vaddq_f64(AA, TEN); // 21.5, 19.5
float64x2 VALIFFALSE = vaddq_f64(AA, FIVE); // 16.5, 14.5
uint64x2_t IF1 = vcgtq_f64 (AA, TEN); // comparison "(if A > 10.)"
vcgtq_f64 的返回值不是一组双精度数,而是两个 64 位无符号整数。它们实际上是一个位掩码,可由“按位选择”函数(例如 vbslq_f64)使用。 IF1 的前 64 位全为 1(大于条件为真),后 64 位全为 0。
AA = vbslq_f64(IF1, VALIFTRUE, VALIFFALSE); // 21.5, 14.5
...并且 AA 的每个通道都根据需要填充该通道的 VALIFTRUE 或 VALIFFALSE。
-
如果急切执行太慢怎么办——一个分支中的计算在处理器时间上是非常昂贵的,如果可以的话,您想完全避免这样做?您必须验证任何向量通道的分支条件都不正确,然后使用适当的“if”语句跳过计算。也许其他人可以评论这在实践中的效果如何。
【讨论】:
有没有办法检测口罩的所有车道是真还是假?哪条车道是真还是假? 这些是整数或按位逻辑运算,不需要内在函数。例如,如果 AA 的所有通道为假,则 AA==0,如果至少一个通道为真,则 AA != 0 那么我应该只是将 64 位或 128 位宽的向量转换为 64 位/128 位整数,然后将其与 0、0xffff...等进行比较?跨度> 【参考方案2】:If-else 激流回旋几乎是所有 CPU 的噩梦,尤其是对于 NEON 等本身没有任何条件分支的向量机。
因此我们对此类问题应用“急切执行”。
创建了一个布尔掩码if
和 else
块均已计算
掩码选择了“正确”的结果
我认为将下面的 aarch32
代码转换为内在函数不会有问题。
//aarch32
vadd.f32 vecElse, vecA, vecTen // vecTen contains 10.0f
vcgt.f32 vecMask, vecA, vecTen
vadd.f32 vecA, vecA, vecFive
vbif vecA, vecElse, vecMask
//aarch64
fadd vecElse.4s, vecA.4s, vecTen.4s
fcmgt vecMask.4s, vecA.4s, vecTen.4s
fadd vecA.4s, vecA.4s, vecFive.4s
bif vecA.16b, vecElse.16b, vecMask.16b
【讨论】:
不知道为什么这得到了反对(甚至没有评论的礼貌),但我会添加赞成票来取消它...... @PaulR 谢谢!我们这里似乎有一些巨魔:-) 是的,我总是对这些随手投降的投票感到困惑,尤其是在问题或答案没有明显错误的情况下。那些人怎么了 ? ;-)【参考方案3】:通常,对于 SIMD 分支逻辑,您使用比较掩码,然后相应地选择替代结果。我将为您的示例提供伪代码,您可以根据需要将其转换为内在函数或 asm:
v5 = vector(5) // set up some constant vectors
v10 = vector(10)
vMask = compare_gt(vA, v10) // generate mask for vector compare A > 10
va = add(vA, v10) // vA = vA + 10 (all elements, unconditionally)
vtemp = and(v5, vMask) // generate temp vector of 5 and 0 values based on mask
va = sub(vA, vTemp) // subtract 5 from elements which are <= 10
【讨论】:
以上是关于如何使用 ARM Neon 内在函数对 IF 块进行矢量化?的主要内容,如果未能解决你的问题,请参考以下文章