如何获得英特尔架构 SIMD __m128 的标志

Posted

技术标签:

【中文标题】如何获得英特尔架构 SIMD __m128 的标志【英文标题】:How do I get the sign of an intel Architecture SIMD __m128 【发布时间】:2018-01-21 02:53:33 【问题描述】:

据我所知,“_mm_sign_ps”不存在:给定一个具有四个浮点值的 __m128 值,SIMD 指令或 SIMD 指令列表会将其转换为具有四个浮点值的 __m128 值点值包含:

+1,如果四个的原始值为正且大于零。 0,如果四个的原始值为零。 -1,如果四个的原始值为负数且小于零。

【问题讨论】:

【参考方案1】:

SSE 确实与此完全不匹配。首先,比较函数不会导致±1.0f,而是如果条件为真则设置所有位,或者如果条件为假则不设置任何位。此外,您要求进行三向比较,如果值为“零”,则结果为“零”(“零”在引号中,因为您实际上并未指定您想要正零还是负零;IEEE 754两者都有)。如果您可以重新定义问题以更好地匹配 SSE 提供的内容,您的情况会好多

也就是说:

__m128 foo (__m128 value) 
  const __m128 zero = _mm_set_ps1 (0.0f);

  __m128 positives = _mm_and_ps(_mm_cmpgt_ps (value, zero), _mm_set_ps1(1.0f));
  __m128 negatives = _mm_and_ps(_mm_cmplt_ps (value, zero), _mm_set_ps1(-1.0f));

  return _mm_or_ps(positives, negatives);

我不知道你打算用它来做什么,但如果你对按位运算感到满意,那么你很有可能会弄清楚如何使用单个 _mm_cmpgt_ps_mm_cmpge_ps_mm_cmplt_ps_mm_cmple_ps

【讨论】:

为了将数轴划分为三个等价类,至少需要进行两次比较。 是的,这就是为什么我在答案中有两个比较。但是,通常没有必要以不同的方式对待零,因此,如果您小心处理问题的方式,则可以将所有内容简化为一个比较,甚至可能不需要将 0xffffffff 转换为 ± 1.0f. 我猜他们可能想乘以这个,(条件符号翻转或零),在这种情况下,它只需要几个布尔值和一个 cmpeq_ps。【参考方案2】:

SSE 不会自然/有效地以这种方式用于浮点/双精度。你到底想用-1.0f / 0.0f / 1.0f sgn(x) 值做什么?

您可能应该优化将这些 FP 值实际保存在寄存器中的步骤,并直接使用比较掩码结果。你问的问题是an X-Y problem 的标志。是的,您实际上可以实现这一点,但通常您不应该这样做。

例如,您可以使用布尔 AND 或 compare+AND 来获取符号位的掩码,然后可以使用布尔 XOR (_mm_xor_ps()) 来翻转另一个设置了这些位的向量中的符号位,然后离开修改对应元素中未设置符号位的元素。

(FP 否定就像翻转符号位一样简单,因为 IEEE-754 二进制格式使用符号/大小表示。)

但要小心-0.0,因为它设置了符号位。如果您想根据相应元素为零来将元素归零,并为其他元素翻转或不翻转,您可以使用几个布尔运算,然后用 _mm_cmpeq_ps 的结果对 0.0 进行屏蔽。 (这对于 0.0 和 -0.0 是正确的)。

例如:

// SSE2  v * sgn(src), except we treat src=NaN as src=0

__m128 mul_by_signum(__m128 v, __m128 src)

    __m128 signbits = _mm_and_ps(src, _mm_set1_ps(-0.0));  // epi32(1<<31)
    __m128 flipped = _mm_xor_ps(v, signbits);

    __m128 nonzero = _mm_cmpne_ps(src, _mm_setzero_ps());
    return _mm_and_ps(flipped, nonzero);


对于整数,有SSSE3 psignb/w/d,它将根据源为正/零/负在目标中保留/零/否定元素。如果目的地为_mm_set1_epi32(1),它将为您提供一个包含 1/0/-1 个元素的向量。

您不能对 FP 数据有用地使用它,因为 FP 使用符号/大小而不是 2 的补码。而且因为它检查整数零,所以-0.0 看起来像一个负数。

顺便说一句,您没有提到您希望 NaN FP 输入发生什么。不要忘记 FP 比较有 4 种可能的结果:高于/等于/低于,或者如果一个或两个操作数都是 NaN,则无序。 (因此,为了与零进行比较,您可以使用正数、零、负数或 NaN)。

【讨论】:

不幸的是,您对 X-Y 问题的看法是正确的。我真正想做的是使用 SIMD 规范化一组 SOA 向量,其中一些从 [0, 0, 0] 开始,但没有任何 +/-Inf 或 NaN 结果结束。符号问题只是我想出的复杂解决方案的一部分。我应该编辑这个问题,还是创建一个新问题? @NarftheMouse:哦,这相对容易。 nonzero_magnitude = _mm_cmpne_ps(sum_of_squares, 0.0);,然后使用它来将产生 0 / 0 的元素的除法(或 rsqrtps)结果掩码为 0.0。因此只需一个额外的 cmpps 和一个额外的 andps 即可将其中的元素归零输入产生 Inf 或 NaN。作为奖励,与早期的临时结果进行比较而不是检查最终结果是否为 NaN 会使比较脱离延迟关键路径。我认为以前有人问过这个问题(至少有一些关于一般想法的问题),但如果你找不到它,一定要问一个新问题。 好的,谢谢。我会在实施后回复您。

以上是关于如何获得英特尔架构 SIMD __m128 的标志的主要内容,如果未能解决你的问题,请参考以下文章

C++:英特尔 SIMD 内部函数类成员的初始化

2. SIMD使用和介绍

计算两个 _m128i SIMD 向量之间的匹配字节数

英特尔 SIMD 内在函数:_mm256_i64scatter_pd

为啥访问单个 SIMD 元素这么慢

SIMD和动态内存分配[重复]