使用 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 指令序列可以改进简单的策略吗?可以假设任意对齐。
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)