将 vec4[idx[i]] * scalar[i] 与 YMM 向量寄存器相加

Summing vec4[idx[i]] * scalar[i] with YMM vector registers

我正在尝试优化以下 sum{vec4[indexarray[i]] * scalar[i]},其中 vec4float[4]scalar 是浮点数。对于 128 位寄存器,这归结为

sum = _mm_fmadd_ps(
            _mm_loadu_ps(vec4[indexarray[i]]),
            _mm_set_ps1(scalar[i]),
            sum);

如果我想在 256 位寄存器上执行 FMA,我将不得不做类似

的事情
__m256 coef = _mm256_set_m128(
                    _mm_set_ps1(scalar[2 * i + 0]),
                    _mm_set_ps1(scalar[2 * i + 1]));
__m256 vec = _mm256_set_m128(
                    _mm_loadu_ps(vec4[indexarray[2 * i + 0]]),
                    _mm_loadu_ps(vec4[indexarray[2 * i + 1]]));
sum = _mm256_fmadd_ps(vec, coef, sum);

随着洗牌并在最后添加以求和上下车道。

理论上,我从单个 FMA 获得 5 的延迟(假设 Haswell 架构),但从 _mm256_set_m128 获得 2x3 的延迟。

有没有一种方法可以使用 ymm 寄存器使其更快,或者来自单个 FMA 的所有增益是否会抵消组合 xmm 寄存器的兴趣?

but lose 2x3 in latency from the _mm256_set_m128

不,延迟不在关键路径上;它是为 FMA 准备输入的一部分。对每个独立 i 值进行更多随机播放的问题是 吞吐量

延迟仅对通过 sum 的循环携带依赖链真正重要,它既是 FMA 的输入又是输出。

仅依赖于 i 和内存内容的输入可以通过 out-of-order 执行在多个迭代中并行处理。


不过,您可能仍然领先,构建 256 位向量。无论您编写源代码(_mm256_set_m128 不是真正的指令),它都可能会在 front-end 或 1-per-clock 洗牌吞吐量上出现瓶颈。您希望它编译为 128 位加载,然后 vinsertf128 ymm, ymm, [mem], 1 插入向量的高半部分。 vinsertf128 确实需要洗牌 uop。

如果您遇到 128 位寄存器的延迟瓶颈,您最好只使用多个累加器,这样(在 Haswell 上)最多 10 个 FMA 可以同时运行:5c 延迟 * 0.5c 吞吐量。在最后添加它们。