AVX512 4D 向量收集的最佳指令序列

问题描述 投票:0回答:1

使用 AVX512 指令,我可以使用索引向量从数组中收集 16 个单精度值。然而,这样的收集操作效率不高,并且在我的机器上仅以 2 个标量负载/周期的速率发出。

在我的一个应用程序中,我总是需要收集四个连续的

float
元素。在标量伪代码中:

for (int i = 0; i < 16; ++i) {
    result.x[i] = source[offset[i]*4 + 0];
    result.y[i] = source[offset[i]*4 + 1];
    result.z[i] = source[offset[i]*4 + 2];
    result.w[i] = source[offset[i]*4 + 3];
}

NVIDIA GPU 可以用一条

ld.global.v4.f32
指令完成一些事情。在 CPU 上,似乎也应该能够利用这种连续性来比 4 个 16 宽收集做得更好。这里有人知道更快的 AVX512 指令序列可以改进简单的策略吗?可以假设任意对齐。

c++ vectorization intel simd avx512
1个回答
0
投票

Intel 和 AMD CPU 上的聚集无法利用源元素彼此连续的优势;它们分别访问每个标量元素的缓存。 (https://uops.info/ - 请注意,尽管最新 Intel 上的端口 2/3 uop 计数较低,但吞吐量与 2/时钟或 3/时钟缓存读取瓶颈相匹配)。

此外,在从 Skylake 到 Tiger Lake 的 Intel 上,由于 GDS 的微码缓解,收集指令吞吐量现在变成了垃圾 (https://downfall.page/#faq)。 AMD 的聚集速度从未如此之快。

正如 chtz 所说,您应该手动执行 128 位 SIMD 加载,因为您的访问模式很简单。


但是我猜你需要对 4 个这样的向量进行洗牌才能获得

x[i + 0..3]
连续的 128 位存储,依此类推,因为你将结果分散到数组结构输出中。

您可以执行两对

vmovups
XMM /
vinsertf128
YMM、m128 对,然后使用
vpermt2ps
将它们混在一起,以获得一个 512 位向量,其中所有四个
x
值都连续,然后所有四个
y
值都连续等(在具有内在函数的 C++ 中,使用
_mm512_castps256_ps512
__m256
重新解释为
__m512
,其上半部分是无关的未定义垃圾。)

这将

vextractf32x4
设置为高位两个内存,
vextractf128
设置为通道 1,
vmovups
设置为通道 0。如果您执行 4x
_mm_store_ps(&result.x[i],  _mm512_extractf32x4_ps(v, 0) )
等操作,希望编译器能够以这种方式进行优化,但您可以手动使用

  __m512 v = _mm512_permutex2var_ps(_mm512_castps256_ps512(first_2_loads),
                                    _mm512_castps256_ps512(second_2_loads),
                                    shuffle_constant);

  _mm_store_ps(&result.x[i],  _mm512_castps512_ps128(v) );  // vmovups
  _mm_store_ps(&result.y[i],  _mm256_extractf128_ps( _mm512_castps512_ps256(v), 1) );   // vextractf128 mem, ymm, 1
  _mm_store_ps(&result.z[i],  _mm512_extractf32x4_ps(v, 2) );  // vextractf32x4 mem, zmm, 2
  _mm_store_ps(&result.w[i],  _mm512_extractf32x4_ps(v, 3) );

或者避免使用 512 位向量并执行两个单独的 256 位

vpermt2ps
如果代码的其余部分不使用 512 位向量

(如果您进行更多迭代,您可以以不同的方式进行洗牌,以设置更广泛的存储,例如 256 个,甚至 512 个(如果值得洗牌的话)。)


AVX-512 有分散指令,但即使在 Intel 上,它们也高效,在 AMD Zen 4 上更差。你可以只使用 Vinsertf128 / Vinsert32x4 等,然后使用包含距离的偏移量进行分散在

x
y
向量的开头之间,假设它们彼此分配在 2GiB 以内,因此可以达到 32 位偏移量。但这会慢得多,我认为甚至无法实现每个时钟 1 个 32 位存储(或者在 Ice Lake 及更高版本上实现 2 个)。但是从存储缓冲区到 L1d 的提交每个时钟只有 1 次,除非两个背靠背存储到同一个缓存行,然后它们可以合并https://travisdowns.github.io/blog/2019/06/11/speed-limits.html#memory-lated-limits)

© www.soinside.com 2019 - 2024. All rights reserved.