我无法弄清楚如何实施:
__m256d min(__m256d A, __m256d B, __m256d C, __m256d D)
{
__m256d result;
// result should contain 4 minimal values out of 16 : A[0], A[1], A[2], A[3], B[0], ... , D[3]
// moreover it should be result[0] <= result[1] <= result[2] <= result[2]
return result;
}
有关如何以智能方式使用_mm256_min_pd
,_mm256_max_pd
和随机播放/置换的任何想法?
=============================================== ===
这是我到目前为止所在的地方:
__m256d T = _mm256_min_pd(A, B);
__m256d Q = _mm256_max_pd(A, B);
A = T; B = Q;
T = _mm256_min_pd(C, D);
Q = _mm256_max_pd(C, D);
C = T; D = Q;
T = _mm256_min_pd(B, C);
Q = _mm256_max_pd(B, C);
B = T; C = Q;
T = _mm256_min_pd(A, B);
Q = _mm256_max_pd(A, B);
A = T; D = Q;
T = _mm256_min_pd(C, D);
Q = _mm256_max_pd(C, D);
C = T; D = Q;
T = _mm256_min_pd(B, C);
Q = _mm256_max_pd(B, C);
B = T; C = Q;
我们有:
A [0]&lt; B [0]&lt; C [0]&lt; d [0],
A [1]&lt; B [1]&lt; C [1]&lt; d [1],
A [2]&lt; B [2]&lt; C [2]&lt; d [2],
A [3]&lt; B [3]&lt; C [3]&lt; d [3],
因此最小值在A&#39之间,第二个最小值在A或B中,...... 不知道从那里去哪里......
=============================================== =========
第二个想法是问题可以自行减少,但有2个输入 __m256元素。如果可以这样做,那么只需做min4(A,B) - &gt; P,min4(C,D) - > Q,min4(P,Q) - &gt;返回值。
不知道如何为两个载体:)
=============================================== ========================
更新2:问题几乎解决了 - 以下函数计算4个最小值。
__m256d min4(__m256d A, __m256d B, __m256d C, __m256d D)
{
__m256d T;
T = _mm256_min_pd(A, B);
B = _mm256_max_pd(A, B);
B = _mm256_permute_pd(B, 0x5);
A = _mm256_min_pd(T, B);
B = _mm256_max_pd(T, B);
B = _mm256_permute2f128_pd(B, B, 0x1);
T = _mm256_min_pd(A, B);
B = _mm256_max_pd(A, B);
B = _mm256_permute_pd(B, 0x5);
A = _mm256_min_pd(A, B);
T = _mm256_min_pd(C, D);
D = _mm256_max_pd(C, D);
D = _mm256_permute_pd(D, 0x5);
C = _mm256_min_pd(T, D);
D = _mm256_max_pd(T, D);
D = _mm256_permute2f128_pd(D, D, 0x1);
T = _mm256_min_pd(C, D);
D = _mm256_max_pd(C, D);
D = _mm256_permute_pd(D, 0x5);
C = _mm256_min_pd(C, D);
T = _mm256_min_pd(A, C);
C = _mm256_max_pd(A, C);
C = _mm256_permute_pd(C, 0x5);
A = _mm256_min_pd(T, C);
C = _mm256_max_pd(T, C);
C = _mm256_permute2f128_pd(C, C, 0x1);
T = _mm256_min_pd(A, C);
C = _mm256_max_pd(A, C);
C = _mm256_permute_pd(C, 0x5);
A = _mm256_min_pd(A, C);
return A;
};
剩下的就是在返回之前按A在递增顺序中对值进行排序。
答案 0 :(得分:3)
最好做一些SIMD比较,减少到8或4(就像你现在的那样)候选者,然后解压缩到向量寄存器中的标量双精度数。这不需要涉及内存往返:vextractf128
高半部分(_mm256_extractf128_pd
),并投下低半部分。也许使用movhlps
(_mm_movehl_ps
)将__m128
的高半部分降低到低半部分(尽管在使用AVX的CPU上,您只需保存一两个代码字节而不是一个立即洗牌;它不像在一些旧CPU上那样快。)
IDK是否可以使用shuffle解压缩或只是存储。也许混合使用两者,以保持随机端口和存储/加载端口繁忙将会很好。显然,每个向量中的低双精度已经作为标量存在,所以这是你不必加载的。 (并且编译器很难通过存储和重新加载作为标量来利用它,甚至是本地数组。)
即使没有非常缩小候选集,在解包之前的一些SIMD比较器可以减少分支标量代码预期的交换/混洗量,减少分支误预测惩罚。
正如我在Paul R的回答中所描述的那样,在标量代码中,您可能会使用插入排序类型的算法。但它更像是一个优先级队列:只插入前4个元素。如果新候选人大于现有最大候选人,请继续前进。否则插入 - 将其排序到按排序顺序维护的4个候选列表中。
我找到了really nice paper on SIMD sorting networks, with specific discussion of AVX。当使用SIMD packed-min / packed-max指令对几个矢量寄存器数据进行排序时,他们会详细介绍所需的shuffle。他们甚至在他们的例子中使用了像_mm512_shuffle_epi32
这样的内在函数。他们说他们的结果适用于AVX,即使他们在他们的例子中使用了AVX-512掩码寄存器。
这只是本文的最后一点,他们谈到合并使用小排序作为大型并行排序的构建块。我无法在任何地方找到他们的实际代码,所以也许他们从未发布他们基准测量的完整实现来制作他们的图表。 :(
顺便说一下,我写了一篇关于float
成员对64位结构进行排序的前一个answer with some not-very-great ideas,但这并不适用于此,因为我只是解决了处理有效载荷的复杂问题(你不知道)有))。
我现在没有时间来完成这个答案,所以我只是发布一个我的想法摘要:
将该纸张的2寄存器方法调整为AVX(或AVX2)。我不确定如何最好地模仿他们的AVX512屏蔽最小/最大指令。 :/我稍后可能会更新。您可能希望通过电子邮件发送作者并询问他们用于对桌面CPU进行基准测试的代码。
无论如何,在成对的regs上使用2-register功能,从4减少到2 regs,然后再减少到1 reg。与您的版本不同,他们会生成一个完全排序的输出寄存器。
尽可能避免跨车道洗牌可能会很棘手。我不确定你是否可以通过使用shufpd(__m256d _mm256_shuffle_pd (__m256d a, __m256d b, const int select);
)在改组时组合来自两个源regs的数据获得任何收益。 256b版本可以在每个通道上执行不同的随机播放,使用4位imm8而不是2位。
这是一个有趣的问题,但遗憾的是我不应该花时间自己写一个完整的解决方案。如果我有时间,我想比较插入排序优先级队列和同一个pqueue的排序网络完全展开的实现,每个元素分别为4,8,12和16个元素。 (在进行标量之前,不同级别的SIMD排序网络)。
我发现的链接:
palignr
将两个单独排序的向量合并为一个排序的8元素向量对。不直接适用于256b双打矢量。shufps
的限制,它使用低效的混合/混合/随机播放在两个向量之间进行混洗。车道内shufpd
的限制略有不同。 本文可能值得仔细研究。它们具有可用于实际SSE向量的算法,并具有可用的随机操作。答案 1 :(得分:1)
这是一个纯粹的“水平”操作,并不适合SIMD - 我怀疑将四个向量存储在内存中,对16个值进行排序,然后将前四个加载到结果向量中会更快: / p>
__m256d min(__m256d A, __m256d B, __m256d C, __m256d D)
{
double buff[16] __attribute__ ((aligned(32)));
_mm256_store_pd(&buff[0], A);
_mm256_store_pd(&buff[4], B);
_mm256_store_pd(&buff[8], C);
_mm256_store_pd(&buff[12], D);
std::partial_sort(buff, buff+4, buff+16);
return _mm256_load_pd(&buff[0]);
}
为了提高性能,您可以实现一个内联自定义排序例程,该例程对16个元素进行硬编码。