LLVM: 多阶段优化的基础设施 第一章(翻译)

1,871 阅读13分钟

摘要

现代编程语言和软件工程原理,给编译器带来诸多问题, 即简单的编译-链接-执行模型无法应对新的挑战,不能给应用程序提供充足的性能。 过程间优化编译与配置文件驱动优化编译,虽能提供应用所需要的性能,但编译时间过长。

LLVM 是一个编译器基础设施的设计与实现,具有独特的多阶段优化系统。广泛支持过程间和配置文件驱动的优化,同时对于商业编译器系统也十分高效。

LLVM 虚拟指令集把系统作为一个整体关联到一起,它使用高级的类型信息,来做低级的系统表象,支持链接时和链接后积极的过程间优化运行时和系统空闲期都可进行优化处理

链接时和链接后的过程间优化,充分考虑真实的使用环境与习惯,把局部性最大化

高级类型信息 high-level type information 都有那些

低级表现 low-level representation 都有那些?紧凑的表现、各种转换等等

本文也对编译器的设计、实现、 LLVM 编译器基础设施结构做了描述,用以证明设计的灵活性。LLVM 编译器基础设施是一个成熟且高效的系统,是多种研究的良好载体。 

一、简介

当代编程语言与实践,想通过提高软件开发的生产效率为编译器提供高级的语意信息,以支持更加可靠、模块化和动态的应用软件。但这些特性有时,反而会降低已编译应用的运行时性能。与此同时,微处理器仍在以惊人的速度发展,管道越来越深、缓存的级别越来越深、内存访问时间(相对于CPU)越来越长。  

为了解决软硬件发展的不协调问题,硬件设计人员暴露了更多的并行执行资源和集成特性,如寄存器重命名引擎和缓存重排序等原编译器开发人员才需要关注的领域。编译器介于当代编程语言和硬件架构之间,要尽可能地优化应用程序:消除程序中非必要开销,高效使用处理器暴露的资源

register-renaming,通过重命名寄存器操作数,处理指令之间的数据依赖关系,是一种管线技术  THE DESIGN SPACE OFREGISTER RENAMING TECHNIQUES 。

reordering buffers,Improved Basic Block Reordering 、 Reordering Buffer Management with a Logarithmic Guarantee in General Metric Spaces、Reordering Buffers for General Metric Spaces∗

LLVM 是如何处理这两个问题的?

上面两个问题的解决方案在概念上非常简单:增加分析和优化的范围,让编译器做得更好,不幸的是,传统的编译方法无法使编译器应对这些新的挑战

本文描述低级虚拟机 LLVM,一个编译器集成设施,非常适合于**当代编程语言和硬件架构的快速发展,**LLVM 将会取得三个非常关键的目标:

  1. 积极的多阶段优化策略,提供最大性能
  2. 作为引领前沿研究和开发的载体,为现在和未来的工程提供坚实的基础
  3. 操作对终端用户(开发人员)透明与标准系统编译器的行为相同(包括实际的编译时间realistic compilation times)

LLVM 提供优秀的应用性能、良好的编译时性能和便捷高效的编译器开发环境。为了更好的理解 LLVM 的设计决策,下面先考察已有方法的处理方式及其缺陷。

1.1 现有的编译系统方法

为了获得高性能可执行文件,人们对编译器系统进行了广泛的方法研究。大多数的编译器使用链接时过程间优化、运行时动态优化、和配置文件驱动优化中的一种或是几种,获得了不错的高性能可执行文件。但使用这几项技术无法同时获取可执行文件的高性能和低编译时间。

1.1.1 链接时过程间优化

过程间优化(整体优化)是一个提供高性能可执行文件的高度有效的技术,其底层观念是尽可能收集程序的信息到一个位置,以增加分析的范围和跨转换单元的转换在过程优化器中,决定可能优化范围的最重要的决定是在那个级别上展现程序?

在现有的过程间优化器中,有两个可用答案:

  • 非常低级别 - 机器码

在过程优化中,大量的研究集中在链接时或运行时,对机器码 [5, 16, 24, 34, 40, 11, 35] 进行优化。这些系统的优势是,它们经常与未修改的前端编译系统工作,允许开发者使用任何编译器。这些系统存在一个非常重要的限制:机器码无法提供足够的高级信息,用来支持积极的过程间分析或转变。这些系统通常进行非常低的转变,如过程间调用寄存器分配、内联和追踪构造(trace construction)。

  • 非常高级别 - 抽象语法树 (AST)

为了解决机器码优化的不足,编译器开发者创造了一种信息保存技术,用来保存源码级的信息,直到链接时仍可访问。在文章 [19, 4, 13, 37, 46] 中,通过在编译时,把高级编译器中间表示(IR)写入磁盘来完成信息的保存。在链接时,链接器会读取程序 AST 的串行版本,组合它们、优化它们、并最终在链接时执行所有的代码产生

IR 磁盘读写代价比较高?

这个方案使用非常低级的方式来做过程间优化(缺乏高级信息),其代价是非常高的。因为所有的编译都需要延迟到链接时,任何一个文件的变更都需要重写编译整个程序。另外,各个编译器的 IRs 通常是专有的,这严重限制了编译器之间的互操作性。事实上在某些情况下,同一编译器的不同版本之间也不能通信,因为它们依赖 IR 的内存布局。

1.1.2 运行时优化

与过程间优化一样,有多种方式的运行时优化。最常见的方法是简单的彻底忽略动态优化(静态编译器就是采用这个方案)。不使用任何运行时优化或监控,程序动态行为会完全丢失。因此,运行时优化是当代系统中获取高性能的常用技术。最常用的动态优化系统的类型有:

  • 高级语言虚拟机

运行时优化和实时编译 JIT 是非常常见的高级语言虚拟机类别。这些虚拟机通常处理非常动态的语言,如 SmallTalk [21]、elf [44]、Java [22]、C# [32],使用机器独立的字节码作为输入(字节码用来在非常高级的层次上,如 AST,编码这些语言)。通过使用虚拟机和非常高级的输入程序表现(representation),这些系统能提供合理的性能、平台的可移植性、安全服务。

很不幸,高级表现(high-level representation)向运行时优化器过程间链接链接时优化器其提出了相同的问题:编译期可进行的优化太少了。如即时编译器,这意味着动态编译器需要花费宝贵的时钟周期来执行普通(mundane**)的优化,如常见的拷贝传播,无法集中资源做更有意义的优化。**另外,这些高级表示,为动态编译器提供了丰富信息源,如有充足的运行时周期,便可进行各种有趣的优化。

这样对启动速度会有影响吗?

  • 架构级虚拟机和动态翻译器

另一方面(At the other end of the spectrum),机器码再优化器和指令集转换器,或是操纵本机代码以获得更高的性能[5],或是在不同的体系结构之间动态地翻译机器代码[16,24]。这些系统的缺点和应用,与机器代码过程间优化器类似:能很好地处理_轨迹形成与优化  _,但依赖高度精确的配置信息,同时不能进行高级的重构转换

Architecture Level Virtual Machines 架构级虚拟机器

Dynamic Translators  动态翻译器

1.1.3 编译时配置驱动优化

配置驱动优化[23]是一个重要的技术,它使用经过估计的运行时行为,提升性能(优化常见案例,而不考虑特殊情况)。传统的集成配置信息到编译系统的方式,划分标准编译和编译链接多阶段,为五个标准的阶段性过程。

编译的第一阶段:编译程序,并在程序中插入分析能力收集运行时的配置信息。

第二阶段:把带有配置信息的的目标文件链接成一个可执行文件,保留配置信息。 

第三阶段:配置驱动优化,要求应用程序的开发人员通过一系列测试,运行生成的可执行文件,用于为应用程序生成配置信息(profile information)。  

第四和第五阶段:重新编译程序(通常从源码开始),使用收集到的配置信息进行再链接, 以优化程序。

配置驱动优化是一个重要的工具,对最终可执行文件的性能有很大的影响,也具有很多不尽如人意的特性。如配置信息只有在非常精确[39, 11]时才有用;在现实中,对程序的使用是多种多样的,配置文件的运行方式可能与用户的使用方式不匹配。因此,静态的配置信息,可能会起相反的作用,即基于配置信息的优化,可能会降低用户实际遇到的情况

测试案例的局部性,基于不充分的案例或是竞争案例而做的优化,在实际的生产环境中不一定有效

更大的问题是,开发者通常不愿意采用基于配置指导的优化,太笨拙了[11]。为了使用这项技术,开发者需要修改构建过程和测试周期,以适应新的五步过程。如果不易编写脚本(如图形化的程序),手动运行程序来构建配置文件信息,更容易出错且代价高昂。

1.2 使用 LLVM 多阶段优化

LLVM 系统架构(参考第二章)将会解决这些在传统编译器系统中发现的问题。

LLVM 系统中的静态编译器,会把源代码编译成,带有高级类型信息的低级别表现:LLVM 虚拟指令集(可参考第三章)。静态编译器便可使用它的产物,执行大量的编译期优化,同时能够把高级信息传递给链接器

链接时,程序被组合成唯一的 LLVM 虚拟指令集代码单元,并且执行过程间优化(可参考第四章中几个高级过程间优化的例子)。程序完全优化后,便可生成机器码,生成本机可以执行代码,同时包含了后续优化阶段需要的程序 LLVM 字节码的拷贝

LLVM 运行时优化器,简单的监视程序执行,收集配置信息。当运行时优化器确定,它可以通过转变提升应用性能时,它可能通过两种方式实现程序的优化:

  • 直接修改已经被优化的机器码
  • 根据附加的 LLVM 字节码生成新的代码

无论采用那种方式,LLVM 字节码都提供重要的高级别控制流、数据流和类型信息,以便进行积极的运行时优化。

即使有充分的表现信息,某些转换的时间代价是十分昂贵的,不适合在运行时进行。针对这类转换,运行时优化器会收集配置信息,序列化到磁盘上。当系统空闲时,离线优化器会执行积极的配置驱动优化。在功能上,离线优化器和链接时等价。它们的不同点在于,离线优化器使用配置和过程间分析信息来提升应用,而链接时优化器无法使用配置信息。

系统会当场(在程序运行中)收集配置信息,尽可能的提供最精确的信息,且完全不影响开发过程。使用LLVM 虚拟指令集,可以把工作从链接时转移到编译时,加快增量重编译。同时,所有的组件都操作相同的表现(IR),它们可以贡献实现的转换。

 1.3 本文的研究贡献

该文献的主要贡献在于它表明,积极的过程间分析和转换,可以在使用高级别类型信息的低级的表现上执行

该文献的第二个主要贡献在于表明,低级的表现如何能够被用来构建一个包含复杂的优化器且实用的编译器系统,它满足以下特性:

• ... 作为预先存在的工具的替代品,它能很好的融入标准构建模型

• ... 无论是否存在配置信息,在链接后和链接时,都支持复杂的过程间分析和转换

• ... 允许运行时优化的新策略,使新策略可以利用,可获得的高级别信息,操作机器码。 

• ... 收集配置信息并当场重新优化程序,获取(allowing for)最最精确的配置信息,收获最高性能的应用。

该文献的第三个主要的贡献是该设计的实现,即 LLVM 基础设施,当前各种研究的坚实载体(可参考第四章)。

 1.4 本文的组织结构

第二章讨论各种工具及其它们如何一起工作;

第三章描述 LLVM 虚拟指令集的特性,适于表现各种常见语言;

第四章描述 LLVM 系统的几个应用,用以说明,低级的表示与类型信息一起使用,也可成功地承载各种积极的分析和转换

第五章在成熟度、生产力和绩效方面评估 LLVM 编译器基础设施;

第六章简要的描述领域内相关的工作;

第七章工作总结。

资料

LLVM- An Infrastructure for Multi-Stage Optimization LATTNER 硕士论文