1)Auto-Vectorization in LLVM — LLVM 21.0.0git documentation
2)
LLVM有两个矢量器:循环矢量器和SLP矢量器。这些矢量器专注于不同的优化机会,并使用不同的技术。SLP矢量化器将代码中的多个标量合并为矢量,而循环矢量化器则扩展循环中的指令,以对多个连续迭代进行操作。
默认情况下,循环矢量器和SLP矢量器都处于启用状态。
3)相关命令参数
clang ... -fno-vectorize file.c
clang -mllvm -force-vector-width=8 ... opt -loop-vectorize -force-vector-width=8 ...
clang -mllvm -force-vector-interleave=2 ... opt -loop-vectorize -force-vector-interleave=2 ...
#pragma clang-loop指令允许为后续的for、while、do-while或基于c++11范围的for循环指定循环矢量化提示。该指令允许启用或禁用矢量化和交织。矢量宽度和交织计数也可以手动指定。以下示例明确启用了矢量化和交织:
4)
#pragma clang loop vectorize(enable) interleave(enable)
while(...) {
...
}
#pragma clang loop vectorize_width(2) interleave_count(2)
for(...) {
...
}
#pragma clang loop vectorize(enable)
for (int i = 0; i < Length; i++) {
switch(A[i]) {
case 0: A[i] = i*2; break;
case 1: A[i] = i; break;
default: A[i] = 0;
}
}
5)Loops with unknown trip count
循环矢量器支持行程计数未知的循环。在下面的循环中,迭代的开始和结束点是未知的,循环矢量化器有一种机制来矢量化不从零开始的循环。在这个例子中,'n'可能不是向量宽度的倍数,矢量器必须将最后几次迭代作为标量代码执行。保留循环的标量副本会增加代码大小。
void bar(float *A, float* B, float K, int start, int end) {
for (int i = start; i < end; ++i)
A[i] *= B[i] + K;
}
6)Runtime Checks of Pointers
在下面的例子中,如果指针A和B指向连续的地址,那么对代码进行矢量化是非法的,因为A的一些元素将在从数组B读取之前被写入。
一些程序员使用“restrict”关键字来通知编译器指针是不相交的,但在我们的例子中,循环矢量器无法知道指针A和B是唯一的。循环矢量器通过放置代码来处理这个循环,这些代码在运行时检查数组A和B是否指向不相交的内存位置。如果数组A和B重叠,则执行循环的标量版本。
void bar(float *A, float* B, float K, int n) {
for (int i = 0; i < n; ++i)
A[i] *= B[i] + K;
}
7)Reductions
在这个例子中,sum变量由循环的连续迭代使用。通常,这会阻止矢量化,但矢量化器可以检测到“sum”是一个缩减变量。变量“sum”变成一个整数向量,在循环结束时,数组的元素被加在一起以创建正确的结果。我们支持多种不同的缩减操作,如加法、乘法、XOR、AND和OR。
int foo(int *A, int n) {
unsigned sum = 0;
for (int i = 0; i < n; ++i)
sum += A[i] + 5;
return sum;
}
8)Inductions
在这个例子中,感应变量i的值被保存到一个数组中。循环矢量器知道对感应变量进行矢量化。
void bar(float *A, int n) {
for (int i = 0; i < n; ++i)
A[i] = i;
}
9)If Conversion
循环矢量器能够“展平”代码中的IF语句,并生成单个指令流。循环矢量器支持最内层循环中的任何控制流。最内层的循环可能包含IF、ELSE甚至GOTO的复杂嵌套。
int foo(int *A, int *B, int n) {
unsigned sum = 0;
for (int i = 0; i < n; ++i)
if (A[i] > B[i])
sum += A[i] + 5;
return sum;
}