现在,机器学习和人工智能这一主题非常流行,由于计算机的计算能力,很长时间以来出现的思想和算法都可以实施并得到显着改善。 几乎每天您都可以阅读有关该领域新成就的新闻。 而且,机器学习几乎在所有领域中都得到了应用……并且编译器的开发也不例外。 但是,该领域非常具体,在创建智能编译器时有其自身的特点和困难。 同时,有关此主题的研究很多,并且已经在学术环境中和各个公司中进行了很长时间的研究。
创建编译器时到底在哪里尝试应用机器学习方法? 为何到目前为止,“智能”编译器还没有成为开发人员日常生活的一部分?
在编译器开发中使用机器学习的选项
让我们从关于机器学习的特定用途的第一个问题开始。 事实是,现代编译器是具有大量优化功能的复杂系统,可让您获得更有效的机器代码。 但是,某些优化和其他任务(例如寄存器分配)是NP完整的,这迫使编译器开发人员使用启发式算法。 结果,大多数编译器都有大量的优化标志,可让您配置所使用的启发式方法。 在LLVM中,几乎每个段落都有几个可能影响其操作的隐藏选项,可以在调用clang时使用–mllvm标志或在opt实用程序中使用它们。 但是,多种标志隐藏在更常用的选项后面,这些选项一次包含许多设置,通常称为优化级别。 对于C / C ++编译器,大多数-O1,-O2,-O3用于优化运行时,而-Os用于优化代码大小是大多数已知的。 但是,不幸的是,最佳代码并不总是结果(汇编专家可以以最佳方式重写生成的代码),这在很大程度上取决于高级语言中的源代码,处理器体系结构,语言功能等。
尽管当今的现代处理器具有足够的RAM和相当高的性能,但在某些领域中,应用程序性能,能效和机器代码大小仍然起着关键作用。 此类领域的示例包括用于RAM数量有限的嵌入式系统的软件开发,数字信号处理,实时系统等。 因此,在需要为足够大的系统获取高性能机器代码的情况下,选择能够产生最佳结果的正确编译选项是一项重要任务。 此外,当实时系统需要计算并最小化平台上特定任务的执行时间时,最坏情况运行时(
WCET )
问题并未消失。 到目前为止,使用有限数量RAM的系统的程序员无法完全依靠编译器,并且常常独立地优化生成的机器代码。
一个人很难预测哪些优化会产生良好的结果,哪些会导致回归,因为为此,您需要对所使用的启发式算法的复杂性有充分的了解,对所使用的编译器的结构和段落有充分的了解,并且还必须充分了解已编译程序的代码,当前的应用程序开发过程是不可能的。 结果,为一个人确定一个程序的最佳编译选项成为穷举搜索选项的各种组合以及性能和代码大小的度量的任务。
此外,编译单元的形式存在限制,您可以使用该编译单元并可以选择选项。 因此,对于C / C ++,这仍然是一个可以包含许多代码的文件,也许可能会以不同的方式进行优化,但这是不可能的。 因此,对于某些开发人员来说,可以在各种情况下进行训练然后对代码进行优化的“智能”编译器是一个梦想。
现有研究与解决方案
自然,多年来,研究人员一直在关注自动选择编译选项的问题。 最著名的项目之一是G. Fursin和他的
MILEPOST GCC团队的研究人员的开发,该版本是gcc编译器的一个版本,它本身可以根据对所得数据样本的先前训练来选择优化过程。 在这项工作中,我们使用了55个特征集来解决该问题,并且基于基于最近邻K算法分配良好解的思想,使用了一个相当简单的模型。 正是这种发展表明,优化遍历的优化可以导致代码的速度是使用标准最大优化选项-O3获得的代码的两倍。
G. Pekhimenko和A.D.也进行了研究。 布朗代表IBM的TPO(
多伦多便携式优化器 )。 他们的主要任务是为优化和代码转换集选择启发式可选值。 为了实施,使用逻辑回归,这使得可以进行有效的罚款设置以加快培训速度。 分类器是在Matlab中构建的。 计算每次通过的使用概率,如果大于50%,则使用该概率。 由于他们在这项研究中试图减少的特性,所以是静态编译时间。
A.Askhari直接为整个程序选择编译选项,以最大程度地减少执行时间,编译时间,代码大小和功耗。 为此,使用了由G. Fursin和A. Lokhmotov(也在
GitHub上开发)开发的
cTuning框架和
Collective Mind框架 。
M. Stephenson和S.Amarasinge也进行了一些研究,
他们为某些特别重要的算法(寄存器分配,数据预取,超
块形成)选择了优化方法。 对于每个功能,相应地使用其自身的特征。 对于该解决方案,使用了遗传算法。 在Open Research Compiler(ORC)上对开发的产品进行了测试。
还有一个
MAGEEC (机器引导节能编译器)项目,其目标有所不同。 开发的基础架构使用机器学习来选择必要的优化,以生成具有高性能计算系统最大能效的代码。 MAGEEC旨在与gcc和LLVM一起使用。 该编译器是较大的TSERO(总软件能耗报告和优化)项目的一部分。
与LLVM直接相关的一项研究是
LLVMTuner ,这是I. Chen和W. Adwe在伊利诺伊大学开发的软件产品。 2017年,提交了一份报告,描述了当时的可用结果。 在这项工作中,我们优化了单个“热”循环。 该框架旨在用于大型程序的自动配置。 LLVMTuner在LLVM IR中间件上运行,使用配置文件识别热循环,然后自动调整它们的启发式。 重点是顶级周期。 选定的周期和所有调用功能将转移到一个单独的模块,该模块将进一步进行必要的优化。 此解决方案使您可以在大型程序上获得更高的性能。
存在的问题
但是,没有广泛使用的编译器可以独立调整优化遍历的启发式方法。 怎么了 如您所知,机器学习方法的有效性和所获得模型的质量取决于正确选择的特征和训练数据的质量(尽管存在对“噪声”数据较不敏感的算法)。 在不了解编译器使用的结构和算法的情况下,很难选择一套完整且足够的特征进行训练,尽管存在相当明确和合乎逻辑的特征,例如循环的大小,循环的退出次数等。 因此,很难一次开发出适用于许多编译器的通用解决方案,并且通常不可能做到这一点。 另外,这可能不是必需的。
由于编译器的开发应在相当短的时间内高效且可行,因此,即使大型公司也自然会基于现成的解决方案来开发其工业编译器。 大多数现代解决方案可以分为两类:在虚拟机上运行,例如JVM-JIT编译器,以及基于LLVM的编译器,LLVM是一种使用类似于RISC的指令实现虚拟机的系统-静态和动态编译器。 当然,仍然有公司自己的解决方案,但是由于缺少用于开发所用技术的大型社区,因此它们的竞争力下降。 结果,如今,许多大型公司(例如Google,Apple,Adobe,ARM)都使用LLVM来开发自己的解决方案。 当然,gcc仍然是C / C ++的主要编译器,其他语言也有其他解决方案,但是无论如何,例如,如果找到了LLVM的解决方案,它将已经覆盖了现有编译器的相当一部分。
训练特征的集合也成为一个大问题,因为多遍编译器强烈地转换了中间表示,并且在初始阶段收集的特征与以后的编译器优化不太相关,因此这些特征很有可能发生变化。 此外,对于不同类型的元素,分别收集特征是有意义的:模块,循环,基本块,因为优化通常被设计为更改LLVM中的特定类型的元素,即使根据此标准,也要分割通道。
但是,首先,出现了确定必须收集特征的要素的问题。 有许多方法可以计算在所有优化过程中可以保存的唯一标识符,例如:
- 基于AST的前端哈希
- 前端解析中分配的唯一编号
- 基于CFG(控制流图)中的弧使用校验和生成的64位数字(类似于LLVM中的PGO(配置文件优化))
但是,当元素可以合并为一个,拆分,创建新的标识符并删除原始的标识符时,您需要在转换期间正确保存这些标识符,这并非易事。
其次,原则上很难在已转换的IR上评估以源代码编写的源循环,基本块等的边界。 例如,由于LLVM采用了多阶段的机器代码生成,因此在基于AsmPrinter中的机器指令生成代码之后,有关机器基本单元的信息会丢失。 因此,还会丢失有关基本块和循环的标识符的信息,例如会测量距函数开头的偏移量,因此,使用这种方法,只有在生成机器代码的阶段才能根据指令以字节数的形式获取偏移量。 但是,在使用机器片段时生成机器代码的后续阶段,可以向其中添加各种对齐方式,这会更改较早考虑的指令的大小,并且还会添加nop指令。 因此,对于大型功能结束时的基本块,计算误差可能非常大,直到完全转移到另一个块/周期为止。 而且尽管可以跟踪和考虑后面阶段中的某些转换,但这不能保证测量的准确性,因为指令的大小可以随链接器而变化。

如您所见,甚至需要进行训练的基础上的属性集合也相当复杂且耗时,并且将来很可能成为经过训练的决策模型的输入集。 对于这些问题,没有明显的解决方案,这使与机器学习相关的即时工作变得复杂,并且由于缺乏足够的数据集而吸引了很多人。 好吧,找到机器学习问题的解决方案,选择模型,方法,确定具有大量属性的正确属性子集的典型困难。 在这种情况下存在。 几乎所有接触过机器学习的人都知道它们,也许这里没有针对编译器的独特知识。
很难预测智能编译器何时会普及。 现代编译器还存在其他问题,该问题不太可能通过此方法解决,目前可能更优先。 但是,编译器已经比它们出现之初变得更加智能,并且此过程将继续进行,尽管它可能比大多数开发人员希望的慢一些。