Hexagon LLVM编译架构介绍(13)

240 篇文章 11 订阅

3.6 使用代码优化

本节介绍以下主题:
■ 软件流水线
■ 自动矢量化
■ 合并功能
■ 链接时间优化

3.6.1 软件流水线

LLVM 包括对软件流水线的支持。 默认情况下禁用流水线 - 要启用它,请使用 -O3 选项(第 3.4.5 节)。

软件流水线尝试通过将循环分成三个不同的部分来提高指令级并行性:

■ prolog 执行设置内核部分所需的初始循环迭代。
■ 内核执行循环稳定状态(结构化为比等效的非流水线循环代码更有效地执行)。
■ 结语执行完成内核部分未完成的任何操作所需的最终循环迭代。

以下示例显示了软件流水线的工作原理:

// Vector multiply and accumulate
int foo(int *a, int *b, int n) {
int i;
int sum = 0;
for (i = 0; i < n; i++) {
sum += a[i] * b[i];
}
return sum;
}

如果没有软件流水线,编译器会为循环生成以下代码:

{
loop0(.LBB0_2, r2)
}
.LBB0_2:
{
r2 = memw(r0++#4)
r4 = memw(r1++#4)
}
{
r3 += mpyi(r4, r2)
nop
}:endloop0

请注意,每次循环迭代执行两个指令包:
■ 第一个数据包加载数组元素a[i] 和b[i]。
■ 第二个数据包将两个数组元素相乘并累加结果。

循环中的指令不能存储在单个数据包中,因为多次累加指令取决于在同一循环迭代中加载的值。

启用软件流水线后,编译器为循环生成以下代码:

{
loop0(.LBB0_2, r2)
}
// prolog, iteration 0
{
r2 = memw(r0++#4)
r4 = memw(r1++#4)
}
// kernel, iteration 1 to n-1
.LBB0_2:
{
r3 += mpyi(r4, r2)
r2 = memw(r0++#4)
r4 = memw(r1++#4)
}:endloop0
// epilog, iteration n-1
{
r3 += mpyi(r4, r2)
}

在上面生成的代码中,prolog 部分(只执行一次)执行数组元素 a[0] 和 b[0] 的初始加载。

内核部分(在循环中执行)将当前加载的数组元素值相乘并累加,然后加载下一组数组元素值。

结语部分(仅执行一次)对来自内核最后一次迭代的值( a[n-1] 和 b[n-1] )执行最终乘法累加。

软件流水线使本示例中的主循环代码能够存储在单个指令包中(而不是非流水线版本中所需的两个包),因为多次累加指令能够对已加载的值进行操作 上一次循环迭代。

3.6.2 自动矢量化

LLVM 包括对自动代码矢量化的支持。 默认情况下,矢量化器是禁用的 - 要启用它,请使用 -fvectorize-loops 选项(第 3.4.16 节)。

矢量化只能与代码优化级别 -O3 一起使用。

自动代码矢量化尝试转换循环代码,以便它可以使用 Hexagon 处理器的 SIMD 功能同时执行多次循环迭代。
例如,考虑以下代码:

char A[400], B[400], C[400];
void run(void) {
int i;
for (i = 0; i < 400; i++)
A[i] = B[i] + C[i];
}

使用 -O3 选项编译它会生成以下目标代码(假设没有循环展开,以保持示例简单):

.LBB0_1:
{
r5 = memb(r2++#1)
r6 = memb(r3++#1)
}
{
r5 = add(r6, r5)
memb(r4++#1) = r5.new
}:endloop0

在这种形式中,所有 400 次循环迭代都被执行,在每次迭代中添加一对字节。
对此代码执行自动矢量化会将循环转换为仅执行 50 次迭代,每次迭代并行添加 8 个字节:

.LBB0_1:
{
r1:0 = memd(r2++#8)
r7:6 = memd(r3++#8)
}
{
r1:0 = vaddub(r7:6, r1:0)
}
{
nop
memd(r4++#8) = r1:0
}:endloop0

生成的矢量化循环的执行速度比原始版本快几倍。

其他转换(例如循环展开和软件流水线)适用于原始循环和矢量化版本,因此矢量化的确切性能增益会有所不同。 但在大多数情况下,执行 8 倍的循环迭代通常要快得多。

为了执行代码矢量化,编译器必须首先能够确定给定循环的许多属性:

■循环通常应该是可矢量化的。 例如,以下循环本质上是不可向量化的:

for (i = 0; i < 400; i++) {
A[i] = A[i-1] + B[i];
}

■ 循环应该执行大量迭代。 例如,如果循环的迭代次数少于 8 次,则可能不值得对其进行矢量化。

如果在编译时迭代次数是已知的,编译器将执行成本收益分析,并且可能会选择不对某些小循环或具有不规则归纳变量和/或边界的循环进行矢量化。

如果迭代次数由运行时函数决定,编译器将使用代码版本控制——即同时保留循环的原始版本和向量化版本——并且在执行运行时检查后,它将选择循环之一 要执行的版本。

■ Hexagon 处理器不支持未对齐的加载,并且当前编译器中未实现对处理未对齐对象以进行矢量化的通用支持。 出于这个原因,在请求向量化的某些情况下,全局对象和通用内存分配请求将与 8 字节边界对齐。 这使编译器能够使用双字加载和存储来访问字节数组(如上例所示)。 在某些情况下,编译器还会考虑使用代码版本控制来确保数据的正确对齐。

在某些情况下,上面列出的限制会导致自动矢量化生成更大和/或更慢的代码,而不是预期的性能改进。 由于这个原因,编译器默认不启用自动矢量化,它的使用必须根据具体情况进行评估。

注意:
向量化器当前仅在嵌套循环的最内层循环上运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值