你能举个例子说明如何对一个循环进行矢量化吗?例如,我有以下循环:
for (i=1; i < N; i++) {
a[i] = b[i]*c[i];
d[i] = a[i-1] + 7;
}
我知道在向量化循环之前不应该存在任何依赖关系,但在显示没有依赖关系之后会有什么依赖关系。它究竟是如何被矢量化的,步骤是什么?
答案 0 :(得分:2)
如果你问的是如何手动对其进行矢量化,或者如果你在询问矢量化编译器是如何做的那样,那么我对你的问题并不十分清楚。因此,我将解释编译器如何执行 1 ,如果需要,您可以随时手动重复相同的步骤。让我们使用矢量化宽度为4的示例。
原始代码:
for (i=1; i < N; i++) {
a[i] = b[i]*c[i];
d[i] = a[i-1] + 7;
}
首先,编译器需要确定迭代n依赖于迭代n-1,这是一个错误依赖,因为没有数据流依赖性 - a[n]
不依赖于a[n-1]
。一旦识别出来,编译器就可以执行loop fission 2 :
for (i=1; i < N; i++) {
a[i] = b[i]*c[i];
}
for (i=1; i < N; i++) {
d[i] = a[i-1] + 7;
}
这两个现在都可以进行矢量化;让我们专注于第一个,但对另一个则是一样的。所以我们的代码是:
for (i=1; i < N; i++) {
a[i] = b[i]*c[i];
}
对于我们的例子,我们假设矢量化宽度为4.矢量化的关键是loop unrolling。所以编译器展开大小为4:
int i = 1;
// Unrolled loop:
for (; i < N-3; i+=4) {
a[i] = b[i] *c[i];
a[i+1] = b[i+1]*c[i+1];
a[i+2] = b[i+2]*c[i+2];
a[i+3] = b[i+3]*c[i+3];
}
// Remainder loop:
for (; i < N; i++) {
a[i] = b[i]*c[i];
}
现在很明显如何进行向量化 - 编译器将展开的循环中相同的指令序列更改为单个向量指令:
int i = 1;
// Unrolled loop:
for (; i < N-3; i+=4) {
a[i:i+3] = b[i:i+3]*c[i:i+3];
}
// Remainder loop:
for (; i < N; i++) {
a[i] = b[i]*c[i];
}
之后,在编译器的后期“降低”阶段,它将为该操作分配实际指令 - 例如,如果这些是启用SSE的体系结构上的单精度浮点数组,则可能使用mulps
。
<子> 1当然,以简化的方式 2请记住,在这种情况下,循环裂变实际上会伤害数据局部性 3编译器实际上不必展开,然后只查找重复项 - 这些步骤通常一起使用。 子>