如何应对(编译)大型代码库的问题?


10

尽管我可以编写代码,但是我还没有从事大型项目的经验。到目前为止,我所做的是要么编写可以在几秒钟内完成编译的小程序(各种c / c ++练习,例如算法,编程原理,思想,范例,或者只是尝试api ...),要么在一些较小的项目上工作使用无需编译的脚本语言(Python,PHP,JS)制作。

问题是,当使用脚本语言进行编码时,每当我想尝试某些可行的方法时,我只要运行脚本,看看会发生什么。如果事情不起作用,我可以简单地更改代码,然后再次运行脚本来再次尝试,然后继续进行直到获得所需的结果。.我的意思是,您不必等待可以编译任何东西,因此,使用大型代码库,对其进行修改,对其进行添加或只是简单地使用它都是非常容易的-您可以立即看到所做的更改。

作为示例,我将使用Wordpress。尝试弄清楚如何为其创建插件非常容易。首先,您首先创建一个简单的“ Hello World”插件,然后为管理面板创建一个简单的界面以熟悉API,然后对其进行构建并使其变得更复杂,同时更改其外观。在每次较小的更改后尝试“如果它有用”和“它如何工作/感觉”之后,必须一遍又一遍地重新编译WP之类的东西的想法似乎效率低下,缓慢而错误。

现在,我该如何使用以编译语言编写的项目来做到这一点?我想为一些开源项目做出贡献,这个问题一直困扰着我。情况因项目而异,其中一些事先被明智考虑的项目将以某种方式“模块化”,而另一些只是一个大问题,需要一次又一次地重新编译。

我想了解更多有关如何正确完成的信息。有哪些通用实践,方法和项目设计(模式?)来应对?在程序员世界中如何称呼这种“模块化”?我应该在Google上寻求更多信息以了解更多信息吗?项目是否经常超出了最初的想法范围,这在一段时间后变得很麻烦?有什么方法可以避免长时间编译设计欠佳的项目?一种以某种方式模块化它们的方法(也许在开发时排除了程序的非重要部分(还有其他想法?))?

谢谢。


4
XKCD和相关的ThinkGeek T恤 * 8')
Mark Booth

1
如果您以足够大的预算从事足够大的项目,则可以获取构建服务器来为您进行编译:)
SoylentGray 2011年

@Chad-我知道,但是现在这只是我的家用gnu / linux台式机:)
pootzko 2011年

@Chad好吧,您是在告诉我们我们需要专用服务器来处理Java(或任何其他编译语言)的批量吗?这真是胡扯
Kolob Canyon

1
@KolobCanyon-不,我说的是您需要达到一定规模才能工作。而且它们现在已经足够便宜了,因为拥有专用于快速编译和自动测试的按需VM很容易,规模并不那么大。
SoylentGray

Answers:


8

就像已经说过的那样,您每次进行小的更改就永远不会重新编译整个项目。取而代之的是,您仅重新编译已更改的代码部分以及所有依赖于此的代码。

在C / C ++中,编译非常简单。您将每个源文件编译成机器码(我们称它们为目标文件* .o),然后所有目标文件链接到一个大的可执行文件中。

就像MainMa提到的那样,某些库内置于单独的文件中,这些文件将在运行时与可执行文件动态链接。这些库在Unix中称为共享对象(* .so),在Windows中称为动态链接库(DLL)。动态库具有许多优点,其中之一就是您无需编译/链接它们,除非有效地更改了它们的源代码。

有一些构建自动化工具可以帮助您:

  • 指定源树的不同部分之间的依赖关系。
  • 仅在已修改的部分中启动准时,谨慎的编译。

最著名的代码(make,ant,maven等)可以自动检测自上次编译以来哪些部分代码已更改,以及确切地需要更新哪些对象/二进制文件。

但是,这带来了必须编写“构建脚本”的(相对较小的)成本。它是一个文件,其中包含有关构建的所有信息,例如定义目标及其依赖项,定义所需的编译器以及要使用的选项,定义构建环境,库路径等……您可能听说过Makefile(非常多)在Unix世界中很常见)或build.xml(在Java世界中非常流行)。这就是他们的工作。


2
Ant(Java)无法确定需要重新编译的内容。它处理工作的琐碎部分,重新​​编译更改的源代码,但根本不了解类依赖关系。为此,我们依靠IDE,如果方法签名的更改方式不需要更改调用代码,它们就会出错。
凯文·克莱恩

@kevincline我第二次说-除非您在build.xml文件中指定其他内容,否则ANT会编译所有内容
Kolob Canyon

7

您不必每次都重新编译整个项目。例如,如果它是C / C ++应用程序,则有可能将其分离为库(Windows中的DLL),每个库都单独编译。

该项目本身通常每天在专用服务器上编译:这些是每晚生成的。该过程可能要花费大量时间,因为它不仅包括编译时间,还包括运行单元测试,其他测试和其他过程所花费的时间。


3
如果我不重新编译全部内容,那么我何时才能有时间玩我的Trebuchet
SoylentGray 2011年

5

我认为到目前为止,所有答案也都暗示着,大型软件项目几乎总是分解成更小的部分。通常每件作品都存储在自己的文件中。

这些片段被单独编译以创建对象。然后将对象链接在一起以形成最终产品。[在某种程度上,这有点像是在乐高积木中建造东西。您不要尝试用一大块塑料模压最终的东西,而是将一堆较小的塑料块组合起来制成。]

将项目分解成可以单独编译的部分,可以进行一些巧妙的事情。

增量建筑

首先,当您更改一件作品时,通常不必重新编译所有作品。一般而言,只要您不更改其他部分与您的部分的交互方式,就无需重新编译其他部分。

这就产生了增量构建的想法。在进行增量构建时,仅重新编译受更改影响的部分。这大大加快了开发时间。没错,您可能仍然需要等待所有内容重新链接,但是与必须重新编译和重新链接所有内容相比,这还是一个节省。(顺便说一句:某些系统/语言确实支持增量链接,因此只需要重新链接已更改的事物即可。这样做的代价通常是糟糕的代码性能和大小。)

单元测试

使用小块可以做的第二件事是查看在组合之前分别测试这些块。这就是所谓的单元测试。在单元测试中,每个单元在与系统的其余部分集成(组合)之前都经过单独测试。通常编写单元测试,以便可以在不涉及系统其余部分的情况下快速运行它们。

在测试驱动开发(TDD)中可以看到应用测试的局限性。在此开发模型中,除非要修复失败的测试,否则不会编写/修改任何代码。

使它更容易

因此,分解事物似乎很不错,但构建项目似乎也需要大量工作:您需要弄清楚我们更改了哪些部分以及哪些内容取决于这些部分,编译每个部分,然后将所有内容链接在一起。

幸运的是,程序员很懒惰*,因此他们发明了许多工具来简化工作。为此,已经编写了许多工具来自动化上述任务。其中最著名的已经提到过(make,ant,maven)。这些工具使您可以定义完成最终项目需要组合的部分以及各部分之间的相互依赖关系(即,如果更改此内容,则需要重新编译)。结果是仅发出一个命令即可确定需要重新编译的内容,对其进行编译并重新链接所有内容。

但这仍然使人们无法弄清事物之间的关系。这是很多工作,正如我之前说的,程序员很懒。因此,他们提出了另一类工具。已经编写了这些工具来为您确定依赖项!这些工具通常是Eclipse和Visual Studio等集成开发环境(IDE)的一部分,但也有一些用于通用和特定应用程序的独立工具(makedep,QMake for Qt程序)。

*实际上,程序员并不是很懒惰,他们只是喜欢把时间花在解决问题上,而不是执行程序可以自动化的重复性任务。


5

这是我可以尝试加快C / C ++构建速度的工作清单:

  • 您是否设置为仅重建已更改的内容?大多数环境默认情况下会执行此操作。如果文件或头文件均未更改,则无需重新编译文件。同样,如果objs / lib中的所有链接都没有更改,则没有理由重建dll / exe。
  • 将永不改变的第三方资料和关联的标头放在某些只读代码库区域中。您只需要标题和关联的二进制文件。您可能永远不需要一次从源代码重建它。
  • 重建所有内容时,我的经验中有两个限制因素是内核数磁盘速度。使用功能强大的硬盘,获得功能强大的四核超线程计算机,性能将会提高。考虑固态硬盘-请记住,便宜的硬盘可能比好的硬盘差。考虑使用RAID来增加硬盘
  • 使用分布式构建系统,例如Incredibuild,它将在网络上的其他工作站上拆分编译。(确保您拥有稳定的网络)。
  • 设置一个统一的版本,以免您不断重载头文件。

以我的经验(虽然不是很多,但是很好),如果您的项目超出“非常小”,磁盘速度就变得无关紧要。只需考虑下一个要点,您将说什么:您正在使用网络来加快编译速度。如果磁盘是一个很大的瓶颈,那么诉诸网络似乎不是一个很好的举措。
R. Martinho Fernandes

另一个便宜的解决方案是在tmpfs中进行编译。如果编译过程受IO限制,则可以大大提高性能。
Artefact2

4

在每次较小的更改后尝试“如果它有用”和“它如何工作/感觉”之后,必须一遍又一遍地重新编译WP之类的想法的想法似乎效率低下,缓慢而错误。

执行解释的内容也非常低效且缓慢,并且(可以说)是错误的。您抱怨的是开发人员PC上的时间要求,但没有进行编译会导致用户 PC 上的时间要求,这可能会更糟。

更重要的是,现代系统可以进行相当高级的增量重建,并且很少需要对整个内容进行重新编译以进行较小的更改-编译后的系统可以包含脚本组件,尤其是UI等组件。


1
我认为我的问题并非要解释为编译方法辩论。相反,我只是询问有关如何正确完成大型(编译)项目的建议。虽然感谢增量重建的想法。
pootzko 2011年

@pootzko:好吧,当您不谈论口译的缺点时,讨论编译的缺点是很不公平的。
DeadMG 2011年

1
不,这不对。这是另一场辩论,与我的问题无关。我并不是说这是不应该讨论的事情。应该,但不是这里。
pootzko 2011年

@pootzko:那么您不应该将大部分问题专门用于列举您不喜欢编译的内容。您应该编写简短得多,更简洁的内容,例如“如何减少大型项目的编译时间?”。
DeadMG 2011年

我不知道我要问别人我应该如何“问”我的问题..?:OI这样做是为了更好地解释我的观点,以便其他人可以更好地理解它,并向我解释如何使用编译语言实现相同/相似的事物。我再次(没有)要求任何人告诉我,解释性语言是否导致用户PC上的时间要求更差。我知道,这与我的问题无关-“用编译语言如何完成”,对不起。其他人似乎想通了什么,我也问了,所以我不认为我的问题是不够清楚..
pootzko

4
  • 部分重建

如果项目实现了正确的编译依赖项DAG,那么您仅需重新编译更改所影响的目标文件就可以摆脱困境。

  • 多重编译过程

同样,假设适当的编译依赖性DAG,则可以使用多个进程进行编译。每个核心/ cpu一项工作是正常的。

  • 可执行测试

您可以创建仅链接特定对象文件的多个可执行文件进行测试。


2

除了MainMa的答案外,我们还刚刚升级了工作的机器。当您不由自主地重新编译整个项目时,我们购买的最好的产品之一就是SSD。

另一个建议是尝试使用其他编译器。过去,我们从Java的编译器切换到Jikes,现在我们继续使用与Eclipse捆绑在一起的编译器(不知道它是否有名称),它可以更好地利用多核处理器。

在进行这些更改之前,我们的37,000个文件项目大约花费了15分钟从头开始编译。更改后,它减少到2-3分钟。

当然,值得再次提一下MainMa的观点。每次看到更改时都不要重新编译整个项目。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.