如何使用 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 等本身没有任何条件分支的向量机。

因此我们对此类问题应用“急切执行”。

创建了一个布尔掩码 ifelse 块均已计算 掩码选择了“正确”的结果

我认为将下面的 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 块进行矢量化?的主要内容,如果未能解决你的问题,请参考以下文章

使用NEON优化ARM的卷积运算

ARM NEON 没有 xor gcc 内在函数

如何在 ARM NEON SIMD 内在函数上编写“a[i]=b[c[i]]”

如何在 Neon 内在函数中使用 if 条件进行并行操作?

使用 ARM neon 内部函数进行深度转换

无法使用 ARM NEON 内在函数设置 4 个 floatx32 的向量