上篇文章介绍 BOLT 时,提到了谷歌推出的 Propeller,可以达到和 BOLT 几乎一样的优化效果,同样是基于 profile 信息进行优化,不过是在链接期间操作的。本文就简介一下 Propeller 技术。

1、简介

Propeller(Profile Guided Optimizing Large Scale LLVM-based Relinker) 是在链接期实现类似 BOLT 优化的架构,而且可以很好地利用分布式构建、增量构建降低优化所需耗时。

Propeller 给链接器添加支持每一个 section 放一个 basic block 的功能(这个功能已经先合入到 LLVM 当中了),通过 profile 信息,在链接期间对整个程序进行 block 布局优化、函数划分、函数重排,同样是对代码布局(code layout) 进行优化。与 BOLT 可以达到几乎一样的优化效果,而内存消耗和时间消耗却可以更少。细分 basic block 到一个 section,这就需要每个 basic block 在符号表都有 label。

1
2
3
4
5
6
-fbasic-block-sections=<value>
   Place each function's basic blocks in unique sections (ELF Only) : 
	   all | labels | none | list=<file>

-funique-basic-block-section-names
   Use unique names for basic block sections (ELF Only)

注:上面之所以这样命名,是为了减小字符串表新增 label 的消耗。

不同于 BOLT 是独立的工具,Propeller 是从 LLVM 的分支拉出来添加上相关的功能,是作为 LLVM 工具链的一部分。但也因为是在链接期进行优化,所以收集完 profile 信息后,需要重新进行编译链接。

2、问题

这部分疑问也是出自 Propeller 的官方文档。这篇文档中有很多与 BOLT 的对比,不过也出现过勘误的点,对于使用者而言,能适用的即是更好的。

2.1、既然都是以 profile 文件进行指导优化,为什么不直接在 PGO 里完成?

这个问题也可以理解为,为什么经过了 PGO+LTO 的优化,BOLT 和 Propeller 还能更进一步地优化?上篇文章讲过,主要是 profile 信息的准确度和匹配程度对优化的效果也会有很大的影响。

普通的 PGO 优化在读取 profile 之后,由于处于编译过程,可能在代码进行一些转换(transformation) 之后就变得不准确或不匹配了,同时由于缺乏上下文敏感(context sensitive)、路径敏感(path sensitive) 信息、跨模块的影响等,导致会留有优化空间。

CSPGO(Context-Sensitive PGO) 技术是给 profile 补充了上下文敏感信息,即函数 inline 后再做一次插桩。不过还是缺乏了路径敏感信息。而 BOLT 和 Propeller 是通过诸如 perf 工具采用 sampling-based PGO 的方式,重新对已经优化过的二进制进行 profile 收集,与最终优化时使用更加准确和匹配。

2.2、basic block 的数量之多,会导致二进制极度的膨胀?

使用 Propeller 方式进行的一个 section 对应一个 basic block,只会创建满足 profile 采样的 basic block,实操证明可以排除 90% 左右的数量。实际上使用 basic block 进行代码布局优化比直接读取整个函数操作的内存消耗更小。最大的挑战是为每个 basic block 的 section 生成调试信息(DebugInfo) 和 CFI(Call Frame Information) 信息。

2.3、BOLT 与 Propeller 的区别。

  • Propeller 是在链接时进行。
  • Propeller 支持分布式构建、增量构建。
  • BOLT 是独立的工具;而 Propeller 以 LLVM 的一部分集成到工具链。
  • BOLT 的输入是二进制和 profile,对二进制进行反汇编、优化、重写。而 Propeller 的输入是缓存的 IR 目标文件和 profile,调用编译器后端重新生成目标文件,然后重新链接。

  • BOLT 需要保留二进制中的重定位信息;而 Propeller 需要生成 basic block 的 label 到符号表中。

参考