使用SIMD AVX计算两个排序数组的对称差异大小

时间:2017-04-06 10:51:11

标签: c++ algorithm sse simd avx

我正在寻找一种优化我正在研究的算法的方法。它是最重复的,因此计算密集型部分是两个任意大小的排序数组的比较,包含唯一的无符号整数(uint32_t)值,以获得它们的对称差异的大小(元素的数量)仅存在于其中一个向量中)。将部署算法的目标机器使用支持AVX2的Intel处理器,因此我正在寻找一种使用SIMD就地执行它的方法。

有没有办法利用AVX2指令来获得两个无符号整数排序数组的对称差异大小?

2 个答案:

答案 0 :(得分:3)

由于两个数组都已排序,因此使用SIMD(AVX2)实现此算法应该相当容易。您只需要同时迭代两个数组,然后当比较两个8个向量的向量时出现不匹配时,您需要解决不匹配问题,即计算差异,并使两个数组索引同步回来,并且继续,直到你到达其中一个数组的末尾。然后只需在其他数组中添加剩余元素(如果有),以获得最终计数。

答案 1 :(得分:1)

除非您的数组很小(例如< = 16个元素),否则您需要将两个已排序数组的合并与用于转储不相等元素的其他代码进行合并。

如果预期对称差异的大小非常小,则使用PaulR描述的方法。 如果预计大小很高(比如元素总数的10%),那么你将在向量化时遇到麻烦。优化标量解决方案要容易得多。

在编写了几个版本的代码后,最快的代码是:

int Merge3(const int *aArr, int aCnt, const int *bArr, int bCnt, int *dst) {
    int i = 0, j = 0, k = 0;
    while (i < aCnt - 32 && j < bCnt - 32) {
        for (int t = 0; t < 32; t++) {
            int aX = aArr[i], bX = bArr[j];
            dst[k] = (aX < bX ? aX : bX);
            k += (aX != bX);
            i += (aX <= bX);
            j += (aX >= bX);
        }
    }
    while (i < aCnt && j < bCnt) {
       ... //use simple code to merge tails

这里的主要优化是:

  1. 在块中执行合并迭代(每块32次迭代)。这样可以简化从(i < aCnt && j < bCnt)t < 32的停止标准。这可以针对大多数元素完成,并且可以使用慢速代码处理尾部。
  2. 以无分支方式执行迭代。请注意,三元运算符被编译为cmov指令,并且比较被编译为setXX指令,因此循环体中没有分支。输出数据与众所周知的技巧一起存储:写入所有元素,但仅为有效元素增加指针。
  3. 我还尝试了什么:

    1. (无矢量化)执行(4 + 4)比特合并,检查重复的连续元素,移动指针,以便跳过4分钟元素(总计): 获得4.95ns vs 4.65ns ---稍微差一点。
    2. (完全矢量化)成对比较4 x 4个元素,将比较结果提取到16位掩码中,通过完美散列函数传递,使用带有128个条目LUT的_mm256_permutevar8x32_epi32来获取8个元素的排序,检查重复的连续元素,使用_mm_movemask_ps + 16-entry LUT + _mm_shuffle_epi8只存储最少4个元素中的唯一元素:得到4.00ns对4.65ns ---稍好一些。
    3. 以下是file with solutionsfile with perfect hash + LUT generator

      P.S。请注意,解决了集合交集的类似问题here。解决方案有点类似于我在上面的第2点所概述的内容。