使用 SIMD 加载 main CPU 寄存器吗?

Does the using of SIMD load main CPU registers?

假设我们的软件开发人员的目标是实现 CPU 性能的绝对最大值。 在今天的 CPU 年代,我们有很多核心,我们可以在缓存中加载数据以加快处理速度,我们还有 SIMD 指令(例如 AVX),允许我们 sum\multiply\do 其他具有项目数组的操作(乘法每个 CPU 时钟 8 个整数)。该指令的缺点是向 SIMD 模块发送数据和指令的成本 + 将矢量类型转换为原始类型的开销(抱歉,我只熟悉 C# 的 Vector)(我们暂时不考虑代码的复杂性)。 据我了解,当我们使用 SIMD 时,CPU 的主要寄存器仅用于向该寄存器发送和接收数据,而用于通用计算的主要 ALU 块此时处于空闲状态。 这是我的问题——使用 SIMD 指令会加载主 CPU 块吗?例如,如果我们有大量不同的计算(假设其中 40% 的计算最好在 SIMD 上 运行,而其中 60% 的计算通常比 运行 更好),SIMD 是否允许我们以这种方式获得性能提升:所有内核性能的 100% + SIMD 提升性能的 n%?

我问这个问题是因为例如使用 GPGPU 我们可以使用 GPU 进行并行计算,而 CPU 在这种情况下仅用于发送和接收数据,所以它一直处于空闲状态,我们可以利用它的性能对延迟任务敏感。

看起来这是关于乱序执行的问题?现代 x64 在 CPU 上有许多执行端口,每个端口都可以在每个时钟周期发送一条新指令(因此在 Intel SkyLake 上可以并行执行大约 8 CPU ops 运行)。其中一些端口处理内存 loads/stores,一些处理整数运算,一些处理 SIMD 指令。

因此,例如,您可以在单个通用寄存器上分配 2 个 AVX 浮点乘数、一个 AVX 按位运算、2 个 AVX 加载、一个 AVX 存储和几个指针算法位循环 [您将不得不等待操作完成 - 延迟]。所以从理论上讲,只要代码中没有可怕的依赖链,您就应该能够让每个端口保持忙碌(或者至少,这是基本目标!)。

简单规则 1:保持执行端口越忙,代码执行得越快。这应该是不言自明的。如果您可以让 8 个端口保持忙碌,那么您所做的工作是您只能让 1 个保持忙碌时的 8 倍。不过总的来说,大多数不值得担心(是的,规则总是有例外)

简单规则 2:当 SIMD 执行端口正在使用时,ALU 不会突然空闲 [您这里的术语错误: ALU 只是 CPU 中进行算术运算的位。通用操作的计算是在 ALU 上完成的,但将 SIMD 单元称为 ALU 也是正确的。您要问的是:使用 SIMD 单元时 CPU 的通用部分会断电吗?答案是否定的...]。考虑这个 AVX2 优化方法(它没有做任何有趣的事情!)

#include <immintrin.h>
typedef __m256 float8;
#define mul8f _mm256_mul_ps

void computeThing(float8 a[], float8 b[], float8 c[], int count)
{
    for(int i = 0; i < count; ++i)
    {
        a[i] = mul8f(a[i], b[i]);
        b[i] = mul8f(b[i], c[i]);
    }
}

由于 a、b 和 c 之间没有依赖关系(我应该通过指定 __restrict 来明确说明),因此两个 SIMD 乘法指令都可以在一个时钟周期内分派 (因为有两个执行端口可以处理浮点乘法)

通用 ALU 并没有突然断电 - 通用寄存器和指令仍在使用! 1. 计算内存地址(对于:a[i]、b[i]、c[i]、d[i]) 2. 将 load/store 放入那些内存位置 3.增加循环计数器 4. 测试是否达到计数?

碰巧我们也在使用 SIMD 单元进行一些乘法...

简单规则 3:对于浮点运算,使用 'float' 或 '__m256' 几乎没有区别。用于计算 float 或 float8 类型的 CPU 硬件完全相同。机器代码编码中只有几个位指定 float/__m128/__m256 之间的选择。

https://godbolt.org/z/xTcLrf