【问题标题】:Fastest way for indexed array stores in AVX512?AVX512中索引数组存储的最快方法?
【发布时间】:2021-01-14 15:25:59
【问题描述】:

我有一个形式的操作:

for (I=0;I<31;I++)
{
 dst[index1[I]]=src1[I];
 dst[index2[I]]=src2[I];
}

所有数据数组都有 128b 个元素。 [编辑]

我不确定在 AVX512 中实现它的最佳方法是什么。我可以在 zmm 寄存器中加载源代码,但之后我能做的最好的事情就是使用提取和 128b 存储。有什么建议吗?

【问题讨论】:

  • 等等,所有数组有128b个元素,甚至是索引? x86-64“只有”有 64 位指针,那有什么意义呢?或者你的意思是只有 dst 和 2 个来源?如果 index1/2 是 unsigned __int128,您是否只想在索引数组时截断它们的指针宽度?这是唯一真正有意义的事情。在我的回答中,我假设索引可能是 32 位以节省空间。
  • 我应该纠正这个。索引数组元素为 32 位。

标签: x86 avx avx512


【解决方案1】:

在当前的 CPU 上,AVX-512 分散指令不是超级快,在 Skylake-X 上每个时钟周期少于一个 64 位元素,在 Ice Lake 上仅超过 1 个 qword/时钟1 . 手动分散应该比在 64 位分散指令方面模拟 128 位分散更好,但如果您好奇,您可以对这两种方式进行基准测试。

特别是如果索引可以在 index1 和 index2 之间重叠(冲突),4 个单独的 128 位存储几乎肯定比检查索引向量对之间的冲突更好。 请注意,从如果idx1[1] == idx2[0],src1 和 src2 中的 4x 个元素会给出不同的最终结果。在原始源顺序中,该 dst 元素会得到src1[1],但如果你不小心,它会得到src2[0]

对于 128 位元素,可能只需使用 vmovdqu xmm(通过 _mm512_castsi512_si128 / _mm_storeu_si128)和 3x _mm512_extracti64x2_epi64 (VEXTRACTI64x2) 存储进行 512 位加载和手动分散。

或 256 位加载和 vmovdqu xmm + vextracti128 存储。但如果你在周围的代码中使用 512 位向量,你不妨在这里使用它们;您已经支付了 CPU 频率和执行端口关闭成本。

如果您可以让编译器执行 64 位索引数据加载,并使用 mov eax, edx / shr rdx, 32 分隔 32 位一半,以节省内存加载/存储端口,这可能会很好。使用 GNU C typedef uint64_t aliasing_u64 __attribute((may_alias,aligned(4)));,这也许是可能的。


脚注 1:例如Skylake-X 上的vpscatterdq zmm 的吞吐量为每 11 个周期一个,最好的情况。或者在冰湖上,每 7 个周期一个。 https://uops.info/

也就是说,每 11 个周期有 4 个 128 位存储。手动 128 位分散可能每 2 个周期至少 1x 128 位存储,甚至可能每个时钟 1 个。或者在 Ice Lake 上可能更快,因为它有 2 个存储端口和 2 个加载端口,以及更宽的前端。

Scatter 指令也是前端的很多微指令:在 SKX 或 ICL 上分别为 26 或 19。


但只是为了好玩,模拟 128 位分散:

我们可以使用 64 位元素散布来模拟 128 位元素散布,例如 _mm512_i32scatter_epi64 (VPSCATTERDQ)。或者 _mm512_i64scatter_epi64 (VPSCATTERQQ) 如果您的索引需要为 64 位,否则为加载索引节省内存带宽。

生成一个索引向量,将连续的 qword 元素对存储到index1[I]*2index1[I]*2 + 1


分散中的比例因子只能是 1、2、4 或 8,与 x86 索引寻址模式相同。如果您可以将“索引”存储为字节偏移量而不是元素偏移量,那么这可能有助于提高效率。否则,您必须首先将索引的每个输入向量加倍(通过将其添加到自身)。

然后将其与增量副本交错,可能与vpermt2d

void scatter(char *dst, __m512i data_lo, __m512i data_hi, __m256i idx)
{
    idx = _mm256_add_epi32(idx,idx);
    __m256i idx1 = _mm256_sub_epi32(idx, _mm256_set1_epi32(-1));

    const __m256i interleave_lo = _mm256_set_epi32(11,3, 10,2, 9,1,  8,0);
    const __m256i interleave_hi = _mm256_set_epi32(15,7, 14,6, 13,5, 12,4);
    __m256i idx_lo = _mm256_permutex2var_epi32(idx, interleave_lo, idx1);  // vpermt2d
    __m256i idx_hi = _mm256_permutex2var_epi32(idx, interleave_hi, idx1);

    _mm512_i32scatter_epi64(dst, idx_lo, data_lo, 8);
    _mm512_i32scatter_epi64(dst, idx_hi, data_hi, 8);
}

https://godbolt.org/z/TxzjWz 显示了它是如何在循环中编译的。 (默认情况下,clang 会完全展开它。)

【讨论】:

    猜你喜欢
    • 2019-08-14
    • 2012-07-13
    • 2010-11-12
    • 2022-10-25
    • 1970-01-01
    • 1970-01-01
    • 2017-10-29
    • 1970-01-01
    • 2014-03-03
    相关资源
    最近更新 更多