我正在考虑更多关于我正在设计的编程语言。我想知道,有什么方法可以最大限度地缩短编译时间?
答案 0 :(得分:10)
今天你的主要问题是I / O.您的CPU比主内存快许多倍,内存比访问硬盘快1000倍。
因此,除非对源代码进行大量优化,否则CPU将花费大部分时间等待读取或写入数据。
请尝试以下规则:
设计编译器以便在多个独立的步骤中工作。目标是能够在不同的线程中运行每个步骤,以便您可以使用多核CPU。它还有助于并行化整个编译过程(即同时编译多个文件)
它还允许您提前加载许多源文件并对其进行预处理,以便实际编译步骤可以更快地运行。
尝试允许独立编译文件。例如,为项目创建“缺少符号池”。缺少符号不应导致编译失败。如果您在某处找到丢失的符号,请将其从池中删除。编译完所有文件后,检查池是否为空。
创建包含重要信息的缓存。例如:文件X使用文件Y中的符号。这样,当Y更改时,您可以跳过编译文件Z(在Y中不引用任何内容)。如果您想更进一步,请将所有在池中定义的符号放在池中。如果文件以添加/删除符号的方式更改,您将立即知道哪些文件受到影响(甚至不打开它们)。
在后台编译。启动编译器进程,检查项目目录是否有更改,并在用户保存文件后立即编译它们。这样,您每次只需编译几个文件而不是一切。从长远来看,您将进行更多编译,但对于用户而言,周转时间将更短(=用户必须等到更改后才能运行编译结果的时间)。
使用“及时”编译器(即在使用文件时编译文件,例如在import语句中)。然后,项目以源代码形式分发,并在第一次运行时进行编译。 Python做到了这一点。要使其执行,您可以在安装编译器期间预编译库。
不要使用头文件。将所有信息保存在一个位置,并在必要时从源生成头文件。也许将头文件保存在内存中,永远不要将它们保存到磁盘中。
答案 1 :(得分:3)
我可以用什么方法最小化编译时间?
答案 2 :(得分:3)
我自己实现了一个编译器,一旦人们开始批量提供数百个源文件,最终不得不看一下。我发现的事情让我很惊讶。
事实证明,你可以优化的最重要的事情不是你的语法。它也不是你的词法分析器或你的解析器。相反,速度方面最重要的是从磁盘读取源文件的代码。磁盘的I / O是慢。真的很慢。您可以通过它执行的磁盘I / O数量来衡量编译器的速度。
所以事实证明,你可以做的最好的事情是加速编译器是将整个文件读入一个大的I / O内存,从RAM中执行所有的lexing,解析等,然后写在一个大的I / O中将结果输出到磁盘。
我与其中一位负责维护Gnat(GCC的Ada编译器)的人讨论了这个问题,他告诉我他实际上已经将所有可能的东西放到RAM磁盘上,这样即使他的文件I / O也只是RAM读取写道。
答案 3 :(得分:2)
在大多数语言中(除C ++之外的其他所有语言),编译单个编译单元的速度非常快。
绑定/链接通常很慢 - 链接器必须引用整个程序而不是单个单元。
除非你使用pImpl习语,否则C ++会受到影响 - 它需要每个对象的实现细节和所有内联函数来编译客户端代码。Java(源到字节码)受到影响,因为语法不区分对象和类 - 您必须加载Foo类以查看Foo.Bar.Baz是否是由Bar静态字段引用的对象的Baz字段Foo类,或Foo.Bar类的静态字段。您可以在两者之间对Foo类的源进行更改,而不是更改客户端代码的源,但仍然必须重新编译客户端代码,因为字节码区分两种形式,即使语法不。 AFAIK Python字节码不区分两者 - 模块是其父母的真实成员。
如果包含的标头多于所需的标头,则C ++和C会受到影响,因为预处理器必须多次处理每个标头,并且编译器会对它们进行编译。最小化头大小和复杂性有帮助,建议更好的模块化将改善编译时间。并不总是可以缓存头编译,因为预处理头时存在的定义可以改变其语义,甚至语法。 如果你经常使用预处理器,那么会受到影响,但实际的编译速度很快;很多C代码使用typedef struct _X* X_ptr
来比C ++更好地隐藏实现 - C头很容易包含typedef和函数声明,从而提供更好的封装。
所以我建议让你的语言隐藏客户端代码的实现细节,如果你是一个包含实例成员和命名空间的OO语言,请使用两种语言来明确访问这两种语言。允许使用真正的模块,因此客户端代码只需要知道接口而不是实现细节。不允许预处理器宏或其他变体机制改变引用模块的语义。
答案 4 :(得分:2)
以下是我们通过测量编译速度及其影响因素所学到的一些性能技巧:
编写一个两遍编译器:字符为IR,IR为代码。 (编写三个 -pass编译器更容易编写字符 - > AST - > IR - >代码,但速度不是很快。)
作为必然结果,没有优化器;编写快速优化器很难。
考虑生成字节码而不是本机机器码。 Lua的虚拟机是一个很好的模型。
尝试使用线性扫描寄存器分配器或Fraser和Hanson在lcc中使用的简单寄存器分配器。
在简单的编译器中,词法分析通常是最大的性能瓶颈。如果您正在编写C或C ++代码,请使用re2c。如果你正在使用另一种语言(你会发现它更令人愉快),请阅读文章,并学习经验教训。
使用maximal munch或iburg生成代码。
令人惊讶的是,GNU汇编程序是许多编译器的瓶颈。如果可以直接生成二进制,请执行此操作。或者查看New Jersey Machine-Code Toolkit。
如上所述,设计您的语言以避免#include
之类的内容。要么不使用接口文件,要么预先编译接口文件。这种策略大大减少了词法分析器的负担,正如我所说,这通常是最大的瓶颈。
答案 5 :(得分:1)
你可以分解多少可兼容的模块,以及你需要多少跟踪它们?
答案 6 :(得分:1)
答案 7 :(得分:1)
这是一个镜头..
如果您的工具链支持,请使用增量编译。 (制作,视觉工作室等)。
例如,在GCC / make中,如果要编译许多文件,但只在一个文件中进行更改,则只编译该文件。
答案 8 :(得分:1)
到目前为止,答案中有一个令人惊讶的缺失:让你做一个无语境的语法等等。要好好看看Wirth设计的语言,比如Pascal& Modula-2的。您不必重新实现Pascal,但语法设计是为快速编译而定制的。然后看看你是否能找到任何关于Anders拉动实施Turbo Pascal的技巧的旧文章。提示:桌子驱动。
答案 9 :(得分:0)
在过去,您可以通过设置RAM驱动器并在那里进行编译来获得显着的加速。不过,不知道这是否仍然适用。
答案 10 :(得分:0)
在C ++中,您可以使用Incredibuild
等工具进行分布式编译答案 11 :(得分:0)
一个简单的方法:确保编译器本身可以利用多核CPU。
答案 12 :(得分:0)
这取决于您编程的语言/平台。对于.NET开发,最小化解决方案中的项目数量。
答案 13 :(得分:0)
答案 14 :(得分:0)
这对编译器有多严重?
除非语法相当复杂,否则解析器的运行速度应该比仅通过输入文件字符索引的速度慢10-100倍。
同样,代码生成应受输出格式限制。
你不应该遇到任何性能问题,除非你正在做一个大型的,严肃的编译器,能够处理包含大量头文件的大型应用程序。
然后您需要担心预编译头,优化传递和链接。
答案 15 :(得分:0)
我没有看到为缩短编译时间做了很多工作。但是我想到了一些想法:
除非您正在编写高度专业化的语言,否则编译时间并不是真正的问题。
答案 16 :(得分:0)
制作一个不吸吮的构建系统!
有大量的程序可能有3个源文件,需要花费一秒钟来编译,但在你走到那么远之前,你必须通过一个需要大约2分钟检查大小的大小的自动脚本文件。一个int
。如果你在一分钟之后去编译别的东西,它会让你坐下几乎完全相同的一组测试。
因此,除非您的编译器 为用户做了一些糟糕的事情,例如更改其int
的大小或更改运行之间的基本功能实现,只需将该信息转储到文件中即可让他们在一秒钟而不是2分钟内得到它。