使用SIMD指令将代码转换为代码

时间:2017-07-31 08:28:23

标签: performance parallel-processing simd

我正在准备考试,正在做一些没有设施的练习。所以我一直在给这个代码,并想知道我是否已将代码转换为SIMD指令。

代码

int A[100000];
int B[100000];
int C=0;

for int(i=0; i < 100000; i++)
    C += A[i] * B[i];

由于没有剩余,我们不需要照顾它。我们还假设它是一个128位寄存器,因此可以计算4个单精度浮点值。

我的结果 - 使用SIMD

int A[100000];
int B[100000];
int C=0;

for int(i=0; i < 100000/4; i += 4)
    C += A[i] * B[i];
    C += A[i+1] * B[i+1];
    C += A[i+2] * B[i+2];
    C += A[i+3] * B[i+3];

使用SIMD指令而不是用多线程编写程序有什么好处?

2 个答案:

答案 0 :(得分:1)

是的,提供的代码应该编译成具有CPU和编译器的SIMD指令。

在具有矢量功能的处理器上,SIMD公开了可大大加速相同并行计算的硬件功能。例如,假设正在处理的数据被定位在连续的存储区域中,SIMD通常由于流RAM访问而更好地利用单个核上的高速缓存。使用多处理,缓存竞争和其他同步开销实际上可能会降低性能,因为各种内核会尝试同时写入数据。除了必须从共享系统内存中读取一条而非四条独立指令外,von-Neumann机器的内在增强也是如此。

始终存在并行执行这些算术运算的逻辑,但需要使用特定的SIMD指令。因此,SIMD倾向于在热循环中使用,其中手动调整使整体优化有意义。

答案 1 :(得分:1)

假设你的第二个循环中省略的花括号只是一个拼写错误,而for循环中的拼写错误,以及你询问乘法浮点数而你的代码显示整数数组的事实,这不会得到很好的矢量化即使编译器看到它。虽然编译器可能会将A和B中的4个值作为单个指令执行,并且在一个指令中执行4次乘法,但是您的代码会强制编译器然后提取4个产品中的每个并按顺序求和,并获得个体SIMD寄存器中的值通常很慢。

如果另一方面你做了这个

float A[100000];
float B[100000];
float C0=0, C1=0, C2=0, C3=0;

for (size_t i=0; i < 100000/4; i += 4)
{
    C0 += A[i+0] * B[i+0];
    C1 += A[i+1] * B[i+1];
    C2 += A[i+2] * B[i+2];
    C3 += A[i+3] * B[i+3];
}
float C = (C0 + C1) + (C2 + C3);

然后一个好的编译器可以对此进行矢量化,因为它现在看到它在每个循环中加载两个SIMD寄存器,将它们相乘,然后它可以将结果添加到总和的SIMD寄存器中,并且仅提取这4个总和并将它们相加一切都在最后。

矢量化编译可以使用SIMD执行此操作,并且不会更改单个总和的评估顺序(FP数学不是关联的)。由于这个原因,通常不允许编译器改变FP数学的顺序(不是没有一些额外的标志允许它在技术上违反语言标准),因此上面的代码可以用SIMD指令精确表示,并且运行得更快(事实上​​,我将循环展开到另一个阶段,因为乘法将成为一个瓶颈)。

这是SIMD的一个技巧,您必须了解并思考如何使用向量指令最好地实现该操作,然后编写代码以执行相同的操作序列,并希望编译器发现您的&完成了。

或者您可以使用内在函数自己编写向量指令,或者使用OpenMP或类似方法更明确地告诉编译器该做什么。

SIMD相对于此类操作的线程的优点之一是您在单个核心中使用更多的硅...因此您不会阻止另一个线程获得周期。在我们的计算网格上,我们通常在任何一台机器上运行许多单线程进程,以保持所有核心始终处于忙碌状态......在这种情况下,使用更多核心来实现此总和是一种虚假经济,您只是在偷窃循环另一个线程可以有用地运行另一个作业。