__m128中至少有4个SP值

时间:2013-07-14 10:17:59

标签: c sse simd

假设有一个__m128变量,其中包含4个SP值,并且您想要最小值,是否有可用的内在函数,或除了值之间的天真线性比较之外的任何其他函数?

正确知道我的解决方案如下(假设输入__m128变量为x):

x = _mm_min_ps(x, (__m128)_mm_srli_si128((__m128i)x, 4));
min = _mm_min_ss(x, (__m128)_mm_srli_si128((__m128i)x, 8))[0];

哪个非常糟糕但它有效(顺便说一句,有_mm_srli_si128但有__m128类型吗?)

2 个答案:

答案 0 :(得分:6)

没有单一的指令/内在,但你可以用两个shuffle和两个分钟来完成:

__m128 _mm_hmin_ps(__m128 v)
{
    v = _mm_min_ps(v, _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3)));
    v = _mm_min_ps(v, _mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 0, 3, 2)));
    return v;
}

输出向量将包含输入向量中所有元素的min,并在整个输出向量中复制。

答案 1 :(得分:3)

Paul R的答案很好! (@Paul R-如果您阅读本节,谢谢!!)我只是想尝试说明它对像我这样的SSE新手来说实际上是如何工作的。当然 我可能在某个地方错了,因此欢迎进行任何纠正!

_mm_shuffle_ps如何工作?

首先,SSE寄存器的索引与您的预期相反,如下所示:

[6, 9, 8, 5] // values
 3  2  1  0  // indexes

这种索引顺序使矢量左移将数据从低索引移到高索引,就像将整数中的位左移一样。最重要的元素在左侧。


_mm_shuffle_ps可以混合两个寄存器的内容:

// __m128 a : (a3, a2, a1, a0)
// __m128 b : (b3, b2, b1, b0)
__m128 two_from_a_and_two_from_b = _mm_shuffle_ps(b, a, _MM_SHUFFLE(3, 2,   1, 0));
//                                                                  ^  ^    ^  ^ 
//                                            indexes into second operand    indexes into first operand
// two_from_a_and_two_from_b : (a3, a2, b1, b0)

在这里,我们只想改组一个寄存器的值,而不是两个。我们可以通过将v作为两个参数传递来实现,就像这样(您可以在Paul R的函数中看到这一点):

// __m128 v : (v3, v2, v1, v0)
__m128 v_rotated_left_by_1 = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3));
// v_rotated_left_by_1 : (v2, v1, v0, v3) // i.e. move all elements left by 1 with wraparound

尽管如此,我还是将其包装在宏中以提高可读性:

#define mm_shuffle_one(v, pattern)  _mm_shuffle_ps(v, v, pattern)

(它不能是函数,因为pattern的{​​{1}}参数在编译时必须是恒定的。)

这里是实际功能的略微修改版本–我添加了中间名称以提高可读性,因为无论如何编译器都会对其进行优化:

_mm_shuffle_ps

为什么要以我们的方式改组元素?以及如何通过两个inline __m128 _mm_hmin_ps(__m128 v){ __m128 v_rotated_left_by_1 = mm_shuffle_one(v, _MM_SHUFFLE(2, 1, 0, 3)); __m128 v2 = _mm_min_ps(v, v_rotated_left_by_1); __m128 v2_rotated_left_by_2 = mm_shuffle_one(v2, _MM_SHUFFLE(1, 0, 3, 2)); __m128 v3 = _mm_min_ps(v2, v2_rotated_left_by_2); return v3; } 操作找到四个元素中最小的元素?

在仅用两个向量化的min操作min进行4个浮点运算时,我遇到了一些麻烦,但是当我手动遵循将min的值组合在一起时,我就理解了一步一步(尽管独自阅读比阅读它可能更有趣)

说我们有min

v

首先,我们[7,6,9,5] v minv的值:

v_rotated_left_by_1

[7,6,9,5] v 3 2 1 0 // (just the indices of the elements) [6,9,5,7] v_rotated_left_by_1 2 1 0 3 // (the indexes refer to v, and we rotated it left by 1, so the indices are shifted) --------- min [6,6,5,5] v2 3 2 1 0 // (explained 2 1 0 3 // below ) 元素下的每一列跟踪 v2的哪些索引被v组合在一起以获得该元素。 因此,按列从左到右:

min

现在第二个v2[3] == 6 == min(v[3], v[2]) v2[2] == 6 == min(v[2], v[1]) v2[1] == 5 == min(v[1], v[0]) v2[0] == 5 == min(v[0], v[3])

min

Voila! [6,6,5,5] v2 3 2 1 0 2 1 0 3 [5,5,6,6] v2_rotated_left_by_2 1 0 3 2 0 3 2 1 --------- min [5,5,5,5] v3 3 2 1 0 2 1 0 3 1 0 3 2 0 3 2 1 下的每一列都包含v3-(3,2,1,0)的每个元素已经与v3的所有元素一起min-因此每个元素都包含最小的整个向量v

使用该函数后,可以使用float _mm_cvtss_f32(__m128)提取最小值:

v

***

这只是一个切线思想,但是我发现有趣的是,这种方法可以扩展到任意长度的序列,在每一步将上一步的结果旋转__m128 min_vector = _mm_hmin_ps(my_vector); float minval = _mm_cvtss_f32(min_vector); (我认为)。 从理论上讲,这很酷-如果您可以同时逐个元素比较两个序列,则可以在对数时间内找到序列的最小/最大 1

1 这扩展到所有水平折叠/缩小,例如sum。相同的洗牌,不同的垂直操作。

但是,AVX(256位向量)使128位边界变得特殊,并且难以改组。如果只需要标量结果,请提取高半部分,以便每一步都将矢量宽度缩小一半。 (就像Fastest way to do horizontal float vector sum on x86一样,对于128位向量,其洗牌比2x 1, 2, 4, 8, ... 2**ceil(log2(len(v)))更有效率,在不使用AVX进行编译时避免了某些shufps指令。)

但是,如果您希望将结果广播到@PaulR的答案之类的每个元素,则需要进行通道内混洗(即在每个通道的4个元素内旋转),然后交换一半,或旋转128位通道