在4个__m256d寄存器

时间:2016-03-11 16:28:12

标签: c++ simd intrinsics avx avx2

我无法弄清楚如何实施:

__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在递增顺序中对值进行排序。

2 个答案:

答案 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排序网络)。

我发现的链接:

答案 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个元素进行硬编码。