7

我更多地考虑我正在设计的编程语言。我想知道,有什么方法可以最小化它的编译时间?

4

17 回答 17

9

您今天的主要问题是 I/O。你的 CPU 比主存快很多倍,内存比访问硬盘快 1000 倍。

因此,除非您对源代码进行大量优化,否则 CPU 将花费大部分时间等待读取或写入数据。

试试这些规则:

  1. 设计您的编译器以在多个独立的步骤中工作。目标是能够在不同的线程中运行每个步骤,以便您可以利用多核 CPU。它还有助于并行化整个编译过程(即同时编译多个文件)

    它还允许您提前加载许多源文件并对其进行预处理,以便实际的编译步骤可以更快地工作。

  2. 尝试允许独立编译文件。例如,为项目创建一个“缺失符号池”。缺少符号不应导致编译失败。如果您在某处发现丢失的符号,请将其从池中移除。编译完所有文件后,检查池是否为空。

  3. 创建包含重要信息的缓存。例如:文件 X 使用文件 Y 中的符号。这样,当 Y 更改时,您可以跳过编译文件 Z(它不引用 Y 中的任何内容)。如果您想更进一步,请将定义的所有符号放入池中的任何位置。如果文件以添加/删除符号的方式发生更改,您将立即知道哪些文件受到影响(甚至无需打开它们)。

  4. 在后台编译。启动编译器进程,检查项目目录是否有更改,并在用户保存文件后立即编译它们。这样,您每次只需要编译几个文件而不是所有文件。从长远来看,您将编译更多,但对于用户而言,周转时间会更短(= 用户必须等到她可以在更改后运行编译结果的时间)。

  5. 使用“即时”编译器(即在使用文件时编译文件,例如在导入语句中)。然后项目以源代码形式分发,并在第一次运行时编译。Python 做到了这一点。要执行此操作,您可以在安装编译器期间预编译库。

  6. 不要使用头文件。将所有信息保存在一个地方,并在必要时从源代码生成头文件。也许将头文件保存在内存中,永远不要将它们保存到磁盘。

于 2009-02-23T10:47:54.733 回答
3

我自己实现了一个编译器,一旦人们开始批量输入数百个源文件,我就不得不查看它。我很惊讶我发现了什么。

事实证明,你可以优化的最重要的东西不是你的语法。它也不是您的词法分析器或解析器。相反,就速度而言,最重要的是从磁盘读取源文件的代码。I/O 到磁盘的速度很。真的很慢。您几乎可以通过它执行的磁盘 I/O 数量来衡量编译器的速度。

所以事实证明,要加速编译器,最好的办法就是在一个大 I/O 中将整个文件读入内存,从 RAM 中进行所有的词法分析、解析等,然后写出结果在一个大 I/O 中写入磁盘。

我与维护 Gnat(GCC 的 Ada 编译器)的负责人之一讨论了这个问题,他告诉我,他实际上曾经把所有他能做的事情都放在 RAM 磁盘上,这样即使他的文件 I/O 也只是 RAM 读取和写入。

于 2009-02-23T18:38:56.690 回答
3

有什么方法可以最小化它的编译时间?

  • 无编译(解释语言)
  • 延迟(及时)编译
  • 增量编译
  • 预编译的头文件
于 2009-02-23T06:38:34.037 回答
2

在大多数语言(除了 C++ 之外的所有语言)中,编译单个编译单元都非常快。

绑定/链接通常很慢 - 链接器必须引用整个程序而不仅仅是单个单元。

C++ 的缺点是——除非你使用 pImpl 习惯用法——它需要每个对象和所有内联函数的实现细节来编译客户端代码。

Java(字节码的源代码)受到影响,因为语法不区分对象和类 - 您必须加载 Foo 类以查看 Foo.Bar.Baz 是否是 Foo 类的 Bar 静态字段引用的对象的 Baz 字段,或 Foo.Bar 类的静态字段。您可以在两者之间更改 Foo 类的源代码,而不更改客户端代码的源代码,但仍然必须重新编译客户端代码,因为字节码区分两种形式,即使语法没有. AFAIK Python 字节码不区分这两者 - 模块是其父级的真正成员。

如果你包含的头文件比需要的多,C++ 和 C 就会受到影响,因为预处理器必须多次处理每个头文件,而编译器会编译它们。最小化标头大小和复杂性会有所帮助,这表明更好的模块化将缩短编译时间。缓存标头编译并不总是可能的,因为在预处理标头时存在的定义可能会改变其语义,甚至语法。

如果大量使用预处理器,C 会受到影响,但实际编译速度很快;许多 C 代码用于typedef struct _X* X_ptr隐藏实现比 C++ 更好 - C 标头可以很容易地由 typedef 和函数声明组成,从而提供更好的封装。

因此,我建议让您的语言对客户端代码隐藏实现细节,如果您是一种同时具有实例成员和命名空间的 OO 语言,请明确访问这两种语言的语法。允许真正的模块,因此客户端代码只需要知道接口而不是实现细节。不允许预处理器宏或其他变体机制改变引用模块的语义。

于 2009-02-23T09:57:08.800 回答
2

以下是我们通过测量编译速度及其影响因素了解到的一些性能技巧:

  • 编写一个两遍编译器:字符到 IR,IR 到代码。(编写一个经过字符 -> AST -> IR -> 代码的三遍编译器更容易,但速度没有那么快。)

  • 作为推论,没有优化器;很难编写一个快速的优化器。

  • 考虑生成字节码而不是本机机器码。Lua的虚拟机是一个很好的模型。

  • 尝试使用线性扫描寄存器分配器或 Fraser 和 Hanson 在lcc中使用的简单寄存器分配器。

  • 在一个简单的编译器中,词法分析通常是最大的性能瓶颈。如果您正在编写 C 或 C++ 代码,请使用re2c。如果您使用另一种语言(您会发现它更令人愉快),请阅读 aboug re2c 的论文并应用所学的课程。

  • 使用最大咀嚼或iburg生成代码。

  • 令人惊讶的是,GNU 汇编器是许多编译器的瓶颈。如果您可以直接生成二进制文件,请这样做。或者查看新泽西机器代码工具包

  • 如上所述,设计您的语言以避免类似#include. 要么不使用接口文件,要么预编译你的接口文件。这种策略极大地减少了词法分析器的负担,正如我所说,这通常是最大的瓶颈。

于 2009-02-24T02:14:14.117 回答
1

埃菲尔对不同的冻结状态有一个想法,重新编译并不一定意味着整个类都被重新编译。

你能分解多少兼容的模块,你有多关心跟踪它们?

于 2009-02-23T07:03:33.400 回答
1

到目前为止,答案中令人惊讶地缺少一件事:让您使用上下文无关语法等。仔细研究 Wirth 设计的语言,例如 Pascal 和 Modula-2。您不必重新实现 Pascal,但语法设计是为快速编译而定制的。然后看看你是否能找到任何关于 Anders 实现 Turbo Pascal 的技巧的旧文章。提示:表驱动。

于 2009-02-23T18:46:35.440 回答
1
  • 使语法简单明了,因此解析起来又快又容易。
  • 对文件包含施加严格的限制。
  • 尽可能允许在没有完整信息的情况下进行编译(例如,C 和 C++ 中的预声明)。
  • 如果可能,一次性编译。
于 2009-02-23T10:50:38.797 回答
1

这是一个镜头..

如果您的工具链支持,请使用增量编译。(制作、视觉工作室等)。

例如,在 GCC/make 中,如果您有许多文件要编译,但只对一个文件进行更改,则只编译该文件。

于 2009-02-23T06:37:07.440 回答
0

制作一个不会烂的构建系统!

那里有大量的程序,可能需要 3 个源文件,编译时间不到一秒钟,但在你走到那一步之前,你必须先完成一个 automake 脚本,该脚本需要大约 2 分钟的时间检查诸如int. 如果你在一分钟后去编译其他东西,它会让你经历几乎完全相同的一组测试。

因此,除非您的编译器用户做了可怕的事情,例如更改其ints 的大小或在运行之间更改基本函数实现,否则只需将该信息转储到一个文件中,让他们在一秒钟内而不是 2 分钟内得到它。

于 2009-02-23T19:04:48.797 回答
0

在 C++ 中,您可以使用Incredibuild等工具进行分布式编译

于 2009-02-23T07:03:00.187 回答
0

这是一个多么严重的编译器?

除非语法非常复杂,否则解析器的运行速度应该比仅通过输入文件字符索引慢 10-100 倍。

同样,代码生成应该受到输出格式的限制。

你不应该遇到任何性能问题,除非你正在做一个大而严肃的编译器,能够处理具有大量头文件的大型应用程序。

然后你需要担心预编译的头文件、优化过程和链接。

于 2009-02-23T18:26:55.800 回答
0

我还没有看到为最小化编译时间所做的太多工作。但有些想法确实浮现在脑海中:

  1. 保持语法简单。复杂的语法会增加你的编译时间。
  2. 尝试使用并行性,使用多核 GPU 或 CPU。
  3. 对现代编译器进行基准测试,看看有哪些瓶颈以及您可以在编译器/语言中做些什么来避免它们。

除非您正在编写一种高度专业化的语言,否则编译时间并不是真正的问题。

于 2009-02-23T18:51:27.863 回答
0
  • 确保在您尝试编译的第一次编译所有内容。例如禁止前向引用。
  • 使用上下文无关语法,这样您就可以在没有符号表的情况下找到正确的分析树。
  • 确保可以从语法中推断出语义,这样您就可以直接构造正确的 AST,而不是通过使用解析树和符号表来构建正确的 AST。
于 2009-02-23T18:11:48.923 回答
0

这取决于您正在编程的语言/平台。对于 .NET 开发,请尽量减少解决方案中的项目数量。

于 2009-02-23T06:39:01.553 回答
0

在过去,您可以通过设置 RAM 驱动器并在那里编译来获得显着的加速。不过,不知道这是否仍然成立。

于 2009-02-23T06:52:34.283 回答
0

一个简单的方法:确保编译器可以原生地利用多核 CPU。

于 2009-02-23T10:12:43.007 回答