【发布时间】:2015-09-20 17:30:09
【问题描述】:
我正在尝试使用跨平台 SIMD 库 ala ecmascript_simd aka SIMD.js,其中一部分是提供一些“水平”SIMD 操作。特别是,库提供的 API 包括 any(<boolN x M>) -> bool 和 all(<boolN x M>) -> bool 函数,其中 <T x K> 是 K 类型为 T 的元素的向量,boolN 是 N 位布尔值,即所有1 或全零,因为 SSE 和 NEON 返回它们的比较操作。
例如,让v 成为<bool32 x 4>(128 位向量),它可能是VCLT.S32 或其他东西的结果。我想计算all(v) = v[0] && v[1] && v[2] && v[3] 和any(v) = v[0] || v[1] || v[2] || v[3]。
这对于 SSE 来说很容易,例如movmskps 将提取每个元素的高位,因此上述类型的 all 变为(使用 C 内在函数):
#include<xmmintrin.h>
int all(__m128 x) {
return _mm_movemask_ps(x) == 8 + 4 + 2 + 1;
}
any 也是如此。
我正在努力寻找明显/好的/有效的方法来使用 NEON 来实现这一点,它不支持像 movmskps 这样的指令。有一种方法是简单地提取每个元素并使用标量进行计算。例如。有一种简单的方法,但也有使用 NEON 支持的“水平”操作的方法,比如VPMAX and VPMIN。
#include<arm_neon.h>
int all_naive(uint32x4_t v) {
return v[0] && v[1] && v[2] && v[3];
}
int all_horiz(uint32x4_t v) {
uint32x2_t x = vpmin_u32(vget_low_u32(v),
vget_high_u32(v));
uint32x2_t y = vpmin_u32(x, x);
return x[0] != 0;
}
(可以使用VPADD 为后者做类似的事情,这可能更快,但基本上是相同的想法。)
还有其他可以用来实现这一点的技巧吗?
是的,我知道 SIMD 向量单元的水平操作不是很好。但有时它很有用,例如mandlebrot 的许多 SIMD 实现将同时对 4 个点进行操作,并在所有点都超出范围时退出内部循环......这需要进行比较,然后进行水平与。
【问题讨论】:
-
movemskps的更有趣的 SSE 指令是ptest。您可以将其用于and或or。我认为 Neon 有相同的指令vtest。我还没有实现这个,但我想你可以在这里找到你的答案fastest-way-to-test-a-128-bit-neon-register-for-a-value-of-0-using-intrinsics。 -
@Zboson:
vtst在这里并不是特别有用,遗憾的是(因为您已经从比较中获得了一个 0/-1 值的向量)。 Nils 来自链接答案的建议(饱和添加 + 读取 Q 位)通常效果不佳,因为 Q 位是粘性的,因此您需要先使用 RMW 清除它。所以通常的方法是在 arm32 上使用多个vpmax/vpmin在 arm64 上使用单个umaxv/uminv。 -
我不知道许多“mandlebrot 的 SIMD 实现将同时在 4 个点上运行,并且当所有这些点都超出范围时退出内部循环......”我一直在做这一段时间我自己(实际上是 8 像素,带有 AVX 用于单浮点)。对于 x86,我使用
ptest,但您似乎已经找到了 ARM 的最佳解决方案:即 min/max 两次使用 arm7,一次使用 arm8。 -
@StephenCanon,在这种情况下,也许您可以向fastest-way-to-test-a-128-bit-neon-register-for-a-value-of-0-using-intrinsics 提供答案。
-
相关:NEON pack vector compare result into bitmap 要求
movmskps等效项。但是,可能不是测试任何元素是否为真之类的正确构建块。 (例如,只打包到 4 个字节而不是 4 位可能更容易,并且测试 0 或 -1 的 32 位整数)