问题 如何在256位AVX向量中找到水平最大值


我有一个__m256d向量,包含四个64位浮点值。
我需要找到向量元素的水平最大值,并将结果存储在双精度标量值中;

我的尝试最终都使用了很多矢量元素的改组,使得代码不是很优雅也没有效率。此外,我发现不可能只留在AVX域。在某些时候,我不得不使用SSE 128位指令来提取最终的64位值。但是,我想在最后的声明中被证明是错误的。

所以理想的解决方案将:
1)仅使用AVX指令。
2)最小化指令数量。 (我希望不超过3-4条说明)

话虽如此,任何优雅/高效的解决方案都将被接受,即使它不符合上述指导原则。

谢谢你的帮助。

-Luigi


7940
2018-03-20 21:48


起源

这是一个艰难的...你只用1个向量做这个吗?或者你有很多你需要找到最大值的向量?你可以(相当)有效地将4个这样的4 x 4矢量转置并行... - Mysticial
@Mysticial:嗯......我正在处理很多向量。然而,处理的简单性并不能证明每次迭代的两个4x4转置操作是合理的。所以我在没有换位的情况下“横向”处理所有内容。我通过这种方式获得了极大的加速,接近4倍,因为我避免了转置的开销。一切都在紧密循环中手动展开4次。但是,当循环结束时,我留下了最后一个AVX向量。我必须找到其四个元素中最大的元素才能将结果存储回我的双精度标量值。因此我的问题...... - Luigi Castelli
如果它不在“紧密循环”中,它甚至性能至关重要吗? - Mysticial
啊:)在这种情况下,最好的方法可能是高度具体的使用方式。换句话说,它在这个级别上不可矢量化,但是你可以将它推向更高的水平...... - Mysticial
请注意,您可以在使用128位指令时保留在AVX域中。实际上有3种指令:AVX256,AVX128和传统的SSE128。前两个和后一个之间的切换是要避免的,它在英特尔上代价高昂(不是AMD),但前两个几乎可以自由混合(你可能需要插入) vzeroupper有时) - Gunther Piez


答案:


我不认为你能比4条指令做得更好:2次洗牌和2次比较。

__m256d x = ...; // input

__m128d y = _mm256_extractf128_pd(x, 1); // extract x[2], and x[3]
__m128d m1 = _mm_max_pd(x, y); // m1[0] = max(x[0], x[2]), m1[1] = max(x[1], x[3])
__m128d m2 = _mm_permute_pd(m1, 1); // set m2[0] = m1[1], m2[1] = m1[0]
__m128d m = _mm_max_pd(m1, m2); // both m[0] and m[1] contain the horizontal max(x[0], x[1], x[2], x[3])

微不足道的修改仅适用于256位向量:

__m256d x = ...; // input

__m256d y = _mm256_permute2f128_pd(x, x, 1); // permute 128-bit values
__m256d m1 = _mm256_max_pd(x, y); // m1[0] = max(x[0], x[2]), m1[1] = max(x[1], x[3]), etc.
__m256d m2 = _mm256_permute_pd(m1, 5); // set m2[0] = m1[1], m2[1] = m1[0], etc.
__m256d m = _mm256_max_pd(m1, m2); // all m[0] ... m[3] contain the horizontal max(x[0], x[1], x[2], x[3])

(另)


13
2018-03-21 07:43



是的,同意......好的解决方案。谢谢。 - Luigi Castelli
如果你需要播放的结果,全256版本在英特尔CPU上是好的,但在Ryzen上它要慢得多。看到 使用SSE / AVX获取存储在__m256d中的值的总和。 (顺便说一下, _mm_unpackhi_pd 比2短字节 _mm_permute_pd,如果您只想要标量结果,请使用它。没有立即需要,并且可以使用2字节的VEX前缀。) - Peter Cordes


为矢量执行此操作的一般方法 v1 = [A, B, C, D] 是

  1. 置换 v1 至 v2 = [C, D, A, B] (交换第0和第2个元素,以及第1个和第3个元素)
  2. 拿最大值;即 v3 = max(v1,v2)。你现在有 [max(A,C), max(B,D), max(A,C), max(B,D)]
  3. 置换 v3 至 v4,交换第0和第1个元素,以及第2个和第3个元素。
  4. 再拿最大值,即 v5 = max(v3,v4)。现在 v5 包含其所有组件中的水平最大值。

特别是对于AVX,可以使用排列 _mm256_permute_pd 并且可以使用最大值 _mm256_max_pd。我没有准确的置换面具,但它们应该非常简单明白。

希望有所帮助。


2
2018-03-21 03:51



我特别喜欢你的解决方案,因为到目前为止它是唯一一个专门使用AVX指令的解决方案,而不会离开256位域。谢谢。 - Luigi Castelli
对不起,我说得太快了...你不能用AVX做到这一点。大多数AVX操作不会跨越128位边界。所以在这种情况下你不能交换第0和第2个元素以及第1和第3个元素。 AVX置换操作仅允许您交换第0个和第1个元素或第2个和第3个元素。 - Luigi Castelli
@LuigiCastelli:我的解决方案可以编写,以便永远不会离开256位域,如果你愿意的话。更换 _mm256_extractf128_pd 通过 _mm256_permute2f128_pd(x, x, 1), __m128d 通过 __m256d,和 _mm_... 通过 _mm256_..., _mm_permute_pd(m1, 1) 通过 _mm256_permute_pd(m1, 5)。 - Norbert P.


//Use the code to find the horizontal maximum
__m256 v1 = initial_vector;//example v1=[1 2 3 4 5 6 7 8]
__m256 v2 = _mm256_permute_ps(v1,(int)147);//147 is control code for rotate left by upper 4 elements and lower 4 elements separately v2=[2 3 4 1 6 7 8 5]
__m256 v3 = _mm256_max_ps(v1,v2);//v3=[2 3 4 4 6 7 8 8]
__m256 v4 = _mm256_permute_ps(v3,(int)147);//v4=[3 4 4 2 7 8 8 6]
__m256 v5 = _mm256_max_ps(v3,v4);//v5=[3 4 4 4 7 8 8 8]
__m256 v6 = _mm256_permute_ps(v5,(int)147);//v6=[4 4 4 3 8 8 8 7]
__m256 v7 = _mm256_max_ps(v5,v6);//contains max of upper four elements and lower 4 elements. v7=[4 4 4 4 8 8 8 8]

//to get max of this horizontal array. Note that the highest end of either upper or lower can contain the maximum
float ALIGN max_array[8];
float horizontal_max;
_mm256_store_ps(max_array, v7);
if(max_array[3] > max_array[7])
{
    horizontal_max = max_array[3];
}
else
{
    horizontal_max = max_array[7];
}

-2
2017-10-30 06:49



浮动向量需要一个额外的步骤,但存储到数组并进行标量比较不是其中一个步骤。你还是想从一开始 extractf128 / 128bit maxps。首先在英特尔CPU上进行内部通道测试并不是更好,在AMD CPU上肯定更糟糕,其中256b AVX操作的价格是128b AVX操作的两倍。无论哪种方式,256b存储然后两个负载 - >标量比较只是愚蠢,而且比一个慢 extractf128。 - Peter Cordes