让我们想象一下,我们有一个软件开发人员,其目标是实现CPU性能的绝对最大化。 在当今的CPU中,我们有许多内核,我们可以将数据加载到缓存中以进行更快的处理,还具有SIMD指令(例如AVX),这些指令可以对项进行求和,乘以其他运算(每个CPU乘以8整数)时钟)。该指令的缺点是将数据和指令发送到SIMD模块的成本,以及将向量类型转换为原始类型的开销(很抱歉,我只熟悉C#的Vector)(我们暂时还没有解决代码复杂性问题)。 据我了解,当我们使用SIMD时,仅用于向该寄存器发送和接收数据的CPU主寄存器和用于通用计算的主ALU块此时处于空闲状态。 这是我的问题-使用SIMD指令会加载主CPU块吗?例如,如果我们有大量不同的计算(假设其中40%的结果最好在SIMD上运行,而60%的结果通常照常运行),那么SIMD是否可以通过这种方式提高性能:100所有内核中有%的性能+ SIMD的提升性能的n%?
我之所以问这个问题,是因为,例如,使用GPGPU,我们可以使用GPU进行并行计算,而在这种情况下,CPU仅用于发送和接收数据,因此它一直处于空闲状态,我们可以利用它的性能来满足敏感需求。延迟任务。
答案 0 :(得分:0)
看起来像是有关乱序执行的问题吗?现代的x64在CPU上具有许多执行端口,每个执行端口都可以在每个时钟周期内调度一条新指令(因此,大约8个CPU操作可以在Intel SkyLake上并行运行)。这些端口中的一些处理内存加载/存储,一些处理整数运算,一些处理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.加载/存储到那些内存位置 3.增加循环计数器 4.测试是否已达到计数?
碰巧的是,我们还利用SIMD单位进行了两次乘法...
简单规则3 :对于浮点运算,使用'float'或'__m256'几乎没有区别。用于计算float或float8类型的相同CPU硬件完全相同。机器码编码中只有几个位指定在float / __ m128 / __ m256之间进行选择。