在 Linux 上更快地编译 C

作者:Christopher W. Fraser

lcc 是一款小巧快速的 C 编译器,现在可在 Linux 上使用。Linux 自带一款非常好的 C 编译器 gcc。为什么还要费心安装第二个呢?因为这两款编译器在权衡取舍上有所不同,因此它们适用于开发周期的不同阶段。gcc 有许多目标和用户,并且包含一个雄心勃勃的优化器。lcc 体积小 75%(如果算上源代码则更小),编译速度更快,并且有助于预防一些移植错误。

对于那些一直想自定义或扩展其编译器的人来说,我们最近出版的书籍《可重定向的 C 编译器:设计与实现》详细介绍了 lcc 的源代码,从而提供了特别详尽的文档。指向 lcc 的源代码、可执行文件、书籍和作者的链接出现在本文末尾。

速度权衡

lcc 速度很快。gcc 实现了更具雄心的全局代码优化器,因此它可以生成更好的代码,尤其是在完全优化选项的情况下,但全局优化需要时间和空间。lcc 实现了一些低成本、高收益的优化,这些优化协同工作,可以快速生成相当不错的代码。

例如,在运行 Linux 的 90 兆赫兹 Pentium 处理器上,lcc 编译自身需要 36 秒。gcc 使用默认编译器选项编译相同的程序(lcc 源代码)需要 68 秒,而使用最高级别的优化则需要 130 秒。代码质量差异较小。gcc 的默认代码重新处理此输入需要 36 秒,与 lcc 的代码相同。gcc 的最佳代码(即优化级别为 3)运行时间为 30 秒,快约 20%。这只是一个数据点,而且这两款编译器都在不断发展,因此您的结果可能会有所不同。当然,人们可以通过在开发中使用 lcc,并在最终发布版本构建中使用 gcc 进行优化来节省时间。

移植代码

实际上,使用两个不同的编译器编译代码有助于暴露移植性错误。如果一个程序有用,并且源代码可用,那么迟早会有人尝试将其移植到另一台机器,或使用另一个编译器编译它,或两者都做。使用新机器或编译器时,出现小故障并不罕见。以下哪种解决方案会为您带来更少的垃圾邮件?是在代码还新鲜时,您自己找到并消除这些瑕疵?还是让移植者在很久之后才获得关于非标准源代码的诊断信息?

lcc 忠实地遵循 ANSI 标准,并且没有实现任何扩展。实际上,有一个选项指示 lcc 警告各种有效的 C 结构,但这些结构会产生未定义的结果,因此在不同的机器上或使用不同的编译器时可能会表现不同。一些程序员主要使用 lcc 的 strict-ANSI 选项,这有助于他们保持代码的可移植性。

交叉编译

与 gcc 一样,lcc 可以配置为交叉编译器,它在一台机器上运行,并为另一台机器编译代码。交叉编译器可以简化具有多个目标平台的程序员的生活。lcc 比大多数交叉编译器更进一步:我们可以,并且通常确实将多个机器的代码生成器链接到编译器的每个版本中。

例如,我们维护 MIPS、SPARC 和 X86 架构的代码生成器。我们既在多个平台上工作,又为多个平台生成代码,因此能够从任何机器为任何目标生成代码非常方便。我们通常将所有三个代码生成器都折叠到所有编译器可执行文件中。运行时选项告诉 lcc 为哪个目标生成代码。如果您不维护多个目标的代码,则可以自由使用仅包含一个代码生成器的 lcc,为省略的每个代码生成器节省大约 50KB 的空间。

一款紧凑的编译器

lcc 体积小巧。带有单个代码生成器的 lcc Linux 可执行文件为 232 KB,其文本段为 192 KB。gcc (cc1) 相应阶段的这两个数字都超过 1 兆字节。lcc 的小尺寸有助于提高其速度,尤其是在配置较低的系统上。紧凑的程序有利于那些希望修改编译器的人。大多数开发人员将使用 lcc 的预构建可执行文件;他们永远不会检查甚至重新编译源代码。但是 Linux 社区特别重视源代码的可用性,部分原因是它允许用户自定义他们的程序或将其用于其他目的。

当配置 Linux PC 代码生成器时,lcc 包含 12,000 行 C 源代码。gcc 的根目录(不包括特定于目标的描述文件)包含 240,000 行代码。当然,其中一些材料不是编译器本身的一部分,但是对于那些最近没有浏览过 gcc 源代码的人来说,这种分离并不立即显而易见。机器特定的模块是最常更改的部分,因为新的目标机器比新的源语言出现得更频繁。用于 Linux PC 的 lcc 特定于目标的模块有 1200 行代码,其中一半重复了样板声明或支持调试器,因此实际的代码生成器少于 600 行。gcc 的特定于目标的模块平均约为 3000 行。这些比较说明了这样一个事实,即这两款编译器体现了不同的权衡取舍,并且没有哪一款在所有方面都优于另一款:gcc 可以生成更好的代码并提供许多选项,而 lcc 更容易理解,但在其他方面则不那么雄心勃勃。

gcc 和 lcc 都使用可重定向的代码生成器,这些生成器部分由目标机器的形式规范驱动,就像解析器可以由其输入语言的形式语法驱动一样。gcc 的代码生成器部分基于我们中的一位(Fraser)在 1970 年代后期首创的技术。lcc 使用一种不同的技术,该技术更简单,但灵活性稍差。

一款可适应的工具

其紧凑的源代码有助于人们改编 lcc。我们中的一位(Hanson)的早期改编版本注入了分析代码,该代码为 lcc 附带的分析器计算执行次数。例如,以下几行

for (<1965>r = 0; <15720>r < 8; <15720>r++)
   if (<15720>rows[r] &&
       <5508>up[r-c+7] &&
       <3420>down[r+c]) { ...

注释了 lcc 测试套件中一个程序的实现源代码,该程序查找将八个皇后放置在棋盘上的所有方法,以使它们互不攻击。尖括号中的数字报告了以下代码片段执行了多少次,这在同一行中可能会有所不同。如果没有 lcc,这个分析器将是一个大项目,但有了 lcc,它就变得很普通。lcc 的其他改编版本包括解释器 (www.cit.gu.edu.au/~sosic/papers/sigplan92.ps.Z)、用于多个目标的代码生成器 (ftp://ftp.cs.princeton.edu/pub/lcc/contrib/)、C++ 编译器、可以跨网络调试的可编程调试器 (http://www.cs.purdue.edu/homes/nr/ldb/) 和可重定向的优化链接器 (http://www.cs.princeton.edu/~mff/mld/)。斯坦福大学的一个小组已改编 lcc 以与全局优化器一起使用 (suif.stanford.edu/suif/suif.html)。至少其中一些工作选择 lcc 而不是 gcc,是因为 lcc 的小尺寸使其看起来更容易理解和更改。许多这些项目是在 lcc 书籍完成之前开始的;我们预计现在有了广泛的文档,会有更多的改编版本。

文学化编程

大多数开发人员将使用 lcc 的预构建可执行文件,并且永远不会研究源代码。然而,Linux 社区期望获得源代码,并且 lcc 以书籍的形式提供了其大部分代码的注释版本。lcc 的注释,就像其小尺寸一样,旨在帮助开发人员修改 lcc。

lcc 是按照 Knuth 所说的“文学化程序”编写的,它将源代码与散文解释交织在一起。两个程序处理此输入。一个程序仅提取 C 源代码,该源代码可以使用任何 C 编译器进行编译。另一个程序处理散文和代码,并为 lcc 书籍输出打字稿。我们从单个来源生成书籍和编译器,因为多个来源很容易彼此失去同步。

关于 X86 代码生成器章节的一个简短片段演示了文学化编程

静态局部变量获得一个生成的名称,以避免与其他同名的静态局部变量冲突

<X86 defsymbol>=
if (p->scope > LOCAL && p->sclass == STATIC)
   p->x.name = stringf("L%d", genlabel(1));

生成的符号已经有一个唯一的数字名称。Defsymbol 只是前缀一个字母以使其成为有效的汇编程序标识符

<X86 defsymbol>+=
else if (p->generated)
   p->x.name = stringf("L%s",p->name);

上述两个显示中的每一个都由尖括号中的“片段标签”和 C 代码的“片段”组成。片段标签命名了正在描述的 C 程序的一部分(此处为 X86 的例程 defsymbol 的版本)。第二个片段中的 += 表示第二个代码片段附加到第一个片段。

这个例子必然很小,但它展示了文学化编程如何让人一次构建一个复杂的程序,并在过程中对其进行解释。lcc 发行版包括可以像往常一样修改的传统 C 代码,但是当某些解释会有所帮助时,可以很容易地从书中的注释代码中获得它。

此示例中未显示的是每个片段中的页码,这些页码指向相邻的片段,以及页边距中的微型索引,这些索引指向定义正在使用的每个标识符的页面。许多读者认为这些微型索引特别有帮助。

可用性

lcc 的 C 源代码和 Linux 可执行文件可通过匿名 ftp 在 URL ftp://ftp.cs.princeton.edu/pub/lcc/ 上获得。它大约 1 兆字节,因此即使使用 14.4kbaud 调制解调器也可以在大约 10 分钟内下载完成。该软件包包括 Dennis Ritchie 的 ANSI C 预处理器,但 lcc 也与 gcc 的预处理器一起使用。与 gcc 一样,lcc 为标准 Linux 汇编器、调试器和 C 库发出汇编代码,因此该软件包不包含任何这些内容。一个子目录收集了由其他人贡献的代码生成器和其他配套软件。该软件包描述了用于与从事 lcc 工作的人员以及使用 lcc 的人员进行通信的邮件列表。

我们关于 lcc 的书籍《可重定向的 C 编译器:设计与实现》(ISBN 0-8053-1670-1)可从 Addison Wesley(电话:800-447-2226)以及 lcc 主页 (http://www.cs.princeton.edu/software/lcc/) 上列出的其他来源获得。

lcc 可免费用于非商业用途。《lcc 书籍》相当于 lcc 的单用户许可证,因此有些人通过简单地在其产品中包含一本该书的副本(并为此收费)来安排商业用途;出版商提供大幅折扣。其他安排也是可能的。

Chris Fraser (cwf@research.att.com) 自 1974 年以来一直从事编译器编写工作。他于 1977 年在耶鲁大学获得计算机科学博士学位,并在新泽西州默里山的 AT&T 贝尔实验室从事计算研究。

Dave Hanson (drh@cs.princeton.edu) 是普林斯顿大学的计算机科学教授。他的研究兴趣包括编程语言设计与实现、软件工程和编程环境。他的网站是:www.cs.princeton.edu/~drh/

加载 Disqus 评论