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

Computing size of symmetric difference of two sorted arrays using SIMD AVX

本文关键字:数组 排序 对称 两个 AVX SIMD 计算 使用      更新时间:2023-10-16

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

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

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

除非你的数组很小(如 <=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指令,因此循环体中没有分支。输出数据使用众所周知的技巧存储:写入所有元素,但仅增加有效元素的指针。

我还尝试过什么:

(
  1. 无矢量化)执行 (4 + 4) 黑调合并,检查连续元素是否有重复项,移动指针以便跳过 4 分钟元素(总共): 获得 4.95ns 与 4.65ns ---稍差。
  2. (完全矢量化)成对比较 4 x 4 个元素,将比较结果提取到 16 位掩码中,通过完美哈希函数传递,使用 128 个条目 LUT 的_mm256_permutevar8x32_epi32对 8 个元素进行排序,检查连续元素是否有重复项,使用 _mm_movemask_ps + 16 个条目 LUT + _mm_shuffle_epi8 在最少 4 个元素中仅存储唯一元素: 获得 4.00ns 与 4.65ns ---略好。

这是包含解决方案的文件和具有完美哈希+ LUT生成器的文件。

附注:请注意,这里解决了集合交集的类似问题。该解决方案有点类似于我上面概述的第 2 点。