11

AVX512CD 包含内在函数_mm512_conflict_epi32(__m512i a),它返回一个向量,如果位中的每个元素a具有相同的值,则在该向量中设置它。有没有办法在 AVX2 中做类似的事情?

我对确切的位不感兴趣,我只需要知道哪些元素是其左侧(或右侧)元素的副本。我只需要知道分散是否会发生冲突。

基本上我需要一个 AVX2 等价物

__mm256i detect_conflict(__mm256i a) {
  __mm256i cd = _mm256_conflict_epi32(a);
  return _mm256_cmpgt_epi32(cd, _mm256_set1_epi32(0));
}

我能想到的唯一方法是使用_mm256_permutevar8x32_epi32()将每个值右移 1(跨通道),然后进行七次比较,屏蔽掉未使用的位,然后_mm256_or_si256()将它们放在一起,这非常慢。

4

1 回答 1

7

TL:DR : 由于全面检测哪些元素冲突的成本很高,因此可能值得做更多的后备工作以换取更便宜的检测。这取决于您的冲突处理选项/策略。

我想出了一种相当有效的方法来检查是否存在冲突,而无需找到它们的位置,例如64-bit integer elements 的答案。它实际上比Skylake-AVX512 的微编码vpconflictd ymm更快,但当然它给你的信息要少得多。(KNL 速度很快vpconflictd)。

如果有任何冲突,您可以对所有元素使用完全标量后备。如果冲突足够罕见以至于分支错误预测不会影响性能,这将很有效。(不过,AVX2 一开始就没有分散指令,所以我不确定你到底需要什么。)

only-left 或 only-right 行为很难,但我的方法可以为您提供哪些元素与任何其他元素发生冲突的掩码(例如v[0] == v[3],将导致两者都conflict[0]conflict[3]真)。这只需要 1 次额外的洗牌,或者在考虑到这个目标的情况下重新设计时可能需要 0 次。

(我起初误读了这个问题;我以为您检查两个方向,而不是谈论大多数功能的两种不同实现选项vpconflictd。实际上,起初我以为您只是想要进行存在/不存在检查,例如bool any_conflicts(__m256i)。)


查找是否存在任何冲突:bool any_conflicts32(__m256i)

8 choose 2总共有 28 个标量比较。那是打包比较的 3.5 个向量。我们的目标应该是使用 4 个向量比较来完成,这为一些冗余留出了空间。

为这些比较创建输入将需要洗牌,其中一些必须是车道交叉。4 个唯一的比较至少需要 4 个向量(包括最初的未洗牌副本),因为 3 选择 2 只有 3。

理想情况下,尽可能少的洗牌是车道交叉,并且有很多ILP用于比较和比较结果的 ORing。如果洗牌不需要矢量洗牌控制,也很好,只需一个imm8. 如果它们在 AMD Ryzen 上不慢也很好,其中 256b 指令被解码为多个 128b 微指令。(有些洗牌比其他洗牌更糟糕,例如vperm2i128非常糟糕;比vpermq交换单个向量的高半部分和低半部分更糟糕。不幸的是,即使使用 ,clang 也会出错,并尽可能-mtune=znver1编译_mm256_permute4x64_epi64成)。vperm2i128

我很早就找到了一个可以实现大部分目标的解决方案:3 次随机播放,4 次比较。洗牌之一是在车道内。它们都使用立即控制字节而不是向量。

// returns a 0 or non-zero truth value
int any_conflicts32(__m256i v)
{
    __m256i hilo       = _mm256_permute4x64_epi64(v, _MM_SHUFFLE(1,0,3,2));  // vpermq is much more efficient than vperm2i128 on Ryzen and KNL, same on HSW/SKL.
    __m256i inlane_rotr1 = _mm256_shuffle_epi32(v, _MM_SHUFFLE(0,3,2,1));
    __m256i full_rotl2 = _mm256_permute4x64_epi64(v, _MM_SHUFFLE(2,1,0,3));

    __m256i v_ir1 = _mm256_cmpeq_epi32(v, inlane_rotr1);
    __m256i v_hilo= _mm256_cmpeq_epi32(v, hilo);           // only really needs to be a 128b operation on the low lane, with leaving the upper lane zero.
                                                           // But there's no ideal way to express that with intrinsics, since _mm256_castsi128_si256 technically leaves the high lane undefined
                                                           // It's extremely likely that casting down and back up would always compile to correct code, though (using the result in a zero-extended register).
    __m256i hilo_ir1 = _mm256_cmpeq_epi32(hilo, inlane_rotr1);
    __m256i v_fl2 = _mm256_cmpeq_epi32(v, full_rotl2);

    __m256i t1 = _mm256_or_si256(v_ir1, v_hilo);
    __m256i t2 = _mm256_or_si256(t1, v_fl2);
    __m256i conflicts = _mm256_or_si256(t2, hilo_ir1);    // A serial dep chain instead of a tree is probably good because of resource conflicts from limited shuffle throughput

    // if you're going to branch on this, movemask/test/jcc is more efficient than ptest/jcc

    unsigned conflict_bitmap = _mm256_movemask_epi8(conflicts);  // With these shuffles, positions in the bitmap aren't actually meaningful
    return (bool)conflict_bitmap;
    return conflict_bitmap;
}

我是如何设计的

我制作了一个包含所有需要检查的元素对的表格,并制作了经过洗牌的操作数可以满足该要求的列。

我从一些可以廉价完成的洗牌开始,结果证明我的早期猜测足够好。

我的设计笔记:

    // 7 6 5 4 | 3 2 1 0

    // h g f e | d c b a
    // e h g f | a d c b    // inlanerotr1 = vpshufd(v)
    // f e d c | b a h g    // fullrotl2 = vpermq(v)

    // d c b a | h g f e    // hilo = vperm2i128(v) or vpermq.  v:hilo has lots of redundancy.  The low half has all the information.

          v:lrot1      v:frotr2     lrotr1:frotl2                (incomplete)
 * ab   [0]v:lrotr1                 [3]lr1:fl2
 * ac                  [2]v:frotl2
 * ad   [3]v:lrotr1                 [2]lr1:fl2
 * ae                                                                           [0,4]v:hilo
 * af                                           [4]hilo:lrotr1
 * ag                  [0]v:frotl2
 * ah                                           [3]hilo:lrotr1

 * bc   [1]v:lrotr1
 * bd                  [3]v:frotl2                               [5]hilo:frotl2
 * be                                           [0]hilo:lrotr1
 * bf                                                                           [1,5]v:hilo
 * bg                               [0]lr1:fl2  [5]hilo:lrotr1
 * bh                  [1]v:frotl2

 * cd   [2]v:lrotr1
 * ce                  [4]v:frotl2  [4]lr1:fl2
 * cf                                           [1]hilo:lrotr1
 * cg                                                                           [2,6]v:hilo
 * ch                               [1]lr1:fl2  [6]hilo:lrotr1

 * de                                           [7]hilo:lrotr1
 * df                  [5]v:frotl2                               [7]hilo:frotl2
 * dg                               [5]lr1:fl2  [2]hilo:lrotr1
 * dh                                                                           [3,7]v:hilo

 * ef   [4]v:lrotr1                 [7]lr1:fl2
 * eg                  [6]v:frotl2
 * eh   [7]v:lrotr1                 [6]lr1:fl2

 * fg   [5]v:lrotr1
 * fh                  [7]v:frotl2

 * gh   [6]v:lrotr1

 */

原来in-lane rotr1 == full rotl2 有很多冗余,所以不值得用。事实证明,所有允许的冗余都v==hilo可以正常工作。

如果您关心哪个结果在哪个元素中(而不仅仅是检查存在/不存在),那么v == swap_hilo(lrotr1)可以代替lrotr1 == hilo. 但我们也需要swap_hilo(v),所以这意味着额外的洗牌。

我们可以改为在 hilo==lrotr1 之后洗牌,以获得更好的 ILP。或者也许有一组不同的洗牌可以给我们一切。也许如果我们考虑 VPERMD 与矢量洗牌控制......


编译器 asm 输出与最佳 asm

gcc6.3-O3 -march=haswell产生

Haswell 有一个随机播放单元(在端口 5 上)。

   # assume ymm0 ready on cycle 0
    vpermq  ymm2, ymm0, 78     # hilo ready on cycle 3 (execution started on cycle 0)
    vpshufd ymm3, ymm0, 57     # lrotr1 ready on cycle 2  (started on cycle 1)
    vpermq  ymm1, ymm0, 147    # frotl2 ready on cycle 5  (started on 2)
    vpcmpeqd  ymm4, ymm2, ymm0  # starts on 3, ready on 4
    vpcmpeqd  ymm1, ymm1, ymm0  # starts on 5, ready on 6
    vpcmpeqd  ymm2, ymm2, ymm3  # starts on 3, ready on 4
    vpcmpeqd  ymm0, ymm0, ymm3  # starts on 2, ready on 3
    vpor    ymm1, ymm1, ymm4    # starts on 6, ready on 7
    vpor    ymm0, ymm0, ymm2    # starts on 4, ready on 5
    vpor    ymm0, ymm1, ymm0    # starts on 7, ready on 8
         # a different ordering of VPOR merging could have saved a cycle here.  /scold gcc
    vpmovmskb       eax, ymm0
    vzeroupper
    ret

因此,考虑到与此序列中其他指令的资源冲突,但假设与仍在流水线中的过去指令没有冲突,最佳情况下的延迟是 8 个周期才能准备好单个向量。(应该是 7 个周期,但是 gcc 重新排序了我的内在函数的依赖结构,将更多的东西依赖于最后一个 shuffle 结果的比较。)

这比Skylake-AVX512vpconflictd ymm更快,后者具有 17c 延迟,每 10c 吞吐量一个。(当然,这为您提供了更多信息,@harold 的模拟需要更多说明)。

幸运的是 gcc 没有重新排序洗牌并引入潜在的回写冲突。(例如,放在vpshufd最后意味着以最早的优先顺序将 shuffle uops 分派到 port5 将vpshufd在与第一个相同的周期中准备好vpermq(1c 延迟与 3c)。) gcc 为一个版本的代码(其中我比较了错误的变量),所以 gcc-mtune=haswell似乎没有考虑到这一点。(也许这没什么大不了的,我还没有测量过对延迟的真正影响是什么。我知道调度程序很聪明地从保留站中挑选微指令以避免实际的回写冲突,但是 IDK 它有多聪明, 即它是否会在vpshufd稍后运行vpermq避免写回冲突,因为它必须提前预测才能看到即将发生的写回冲突。它更有可能只是vpshufd在调度它之前延迟一个额外的周期。)

无论如何,这就是我将_mm_shuffle_epi32C 源代码放在中间的原因,它使 OOO 执行变得容易。

Clang 4.0开始发狂,将每个比较结果压缩为 128b 向量(使用vextracti128/ vpacksswb),然后在 pmovmskb 之前的三个之后扩展回 256b vpor xmm。起初我以为它这样做是因为-mtune=znver1,但它也这样做了-mtune=haswell。即使我们返回 a bool,它也会这样做,这将让它只是pmovmskb/test在压缩向量上。/掌心。vperm2i128即使使用-mtune=znver1(Ryzen),它也将 hilo shuffle 悲观vperm2i128为 8 微秒但vpermq为 3。(Agner Fog 的 insn 表由于某些原因错过了这些,所以我从 FP 等价物中获取了这些数字,vperm2f128并且vpermpd

@harold 说使用add而不是or停止打包/解包,但vpaddd吞吐量低于vpor英特尔前 Skylake。

对 Ryzen 来说更好的是,v == hilo比较只能做下半部分。(即 use vpcmpeqd xmm2, xmm2, xmm3,它只有 1 uop 而不是 2)。不过,我们仍然需要完整hilo的 for hilo == lrot1。所以我们不能只使用vextracti128 xmm2, xmm0, 1而不是vpermq随机播放。 vextracti128在 Ryzen 上具有出色的性能:1 uop、1c 延迟、0.33c 吞吐量(可以在任何 P0/1/3 上运行)。

由于我们将所有内容组合在一起,因此可以在高半部分使用零而不是冗余比较结果。

正如我在评论中指出的那样,IDK 如何使用内在函数安全地编写它。显而易见的方法是使用_mm256_castsi128_si256 (_mm_cmpeq_epi32(v, hilo)),但从技术上讲,这会使高车道未定义,而不是零。除了使用包含 xmm 寄存器和 128b 比较结果的全角 ymm 寄存器之外,编译器没有任何理智的方法可以做任何事情,但根据英特尔的文档,Deathstation-9000 编译器将垃圾放在那里是合法的。在高半部分获得零的任何显式方法都取决于编译器对其进行优化。也许_mm256_setr_si128(cmpresult, _mm_setzero_si128());


当前没有带有 AVX512F 但没有 AVX512CD 的 CPU。但是,如果该组合有趣或相关,clang 从我的代码中使用-mavx512f -mavx512vl. 它使用 EVEXvpcmpeqd到掩码寄存器中,korw并将它们合并。但随后它将其扩展回一个要设置的向量vpmovmaskb,而不是仅仅优化移动掩码并使用korw结果。/掌心。

于 2017-07-01T13:03:25.480 回答