使用 distcc 缩短编译时间

快速、免费的分布式 C/C++ 编译方法

Comments

考虑到开放源代码软件的一些特性,很多 Linux™ 应用程序被分配到包含源代码的“tarball”中,在运行应该程序之前,需要编译这些源代码。相当多的应用程序的建立都需要花费几个小时的时间。在本文中,我们讨论了如何使用分布式 C 编译器 distcc 来加速编译这些源代码,这样您就可以更快地使用它们。

一些 Linux 应用程序可以用作 RPM(Red Hat Package Manager)文件。对最终用户来说,这些通常是快速安装并运行应用程序的一个好方法。不过,用户常常还有一个选择,尤其是对开放源软件,这个选择就是 .tar.gz(即“tarball”),它通常包含最终用户需要编译的源代码。尽管相对于同族的 RPM 来说,建立 tarball 稍微需要点技巧,但它有它的一些优势:

  • 该应用程序通常都安装在 /usr/local 中,这意味着您可以很轻松地在您的机器上删除这些安装程序,同时仍保留您自己的应用程序
  • 您可以稍微调整代码,使其更合乎您的需要
  • 有很多优化方案可用

最后一点是我个人最欣赏的。作为一个具有一定能力的用户,我希望能够将程序对我的 Athlon XP 处理器 的使用发挥到极致;我可以在编译时完成这一任务。但有一个警告:启用优化意味着要增加编译时间 —— 编译器尝试 去做一些智能的事情,例如追随循环并分析出常量。最终结果是牺牲编译时间来换取极快的代码。

让我们来看一个典型的应用程序:OpenSSH。我刚从网站上(请参见 参考资料 ) 下载了 openssh-3.7p1.tar.gz tarball,并打算将它用作“测试”应用程序。

首先,我提取了 tarball:

me@mymachine:~> tar xvzf openssh-3.7p1.tar.gz

在这里, x 提取了 tar 文件, v 给出了详细的输出, z 告诉 tar 命令去 gunzip(解压缩)文件, f 是我希望提取的 tar 文件。如果是 .tar.bz2 文件,我会用 j 来取代 z ,用它来指出 tar 应该 bunzip 文件而不是 gunzip 它。(在命令行中输入 man tar 可以得到 手册页提及的有关 tar 及其选项的更多资料)

然后切换到新创建的 openssh 目录:

me@mymachine:~> cd openssh-3.7p1

我将为 gcc 指定一些编译器选项,因为这是我建立源代码将使用到的工具,并且我希望能够利用我机器的一些特性。由于我使用的是 bash,我将使用 export 命令(在 tcsh 或其他类似 shell 中使用 setenv 命令):

me@mymachine:~/openssh-3.7p1> export CFLAGS="-O3 -march=athlon-xp \
-funroll-loops -fexpensive-optimizations"
me@mymachine:~/openssh-3.7p1> export CXXFLAGS=$CFLAGS

注意 -march 标志。由于我的工作站中有一个 AMD Athlon XP 处理器,所以我可以使用 gcc 3.x 中 的这个便捷 -march=athlon-xp 开关来自动开启处理器特定优化,比如 SSE。我还可以使用 -march=pentium4 或者 -march=pentiumpro , 或者完全忽略它们。请查看 gcc 的手册页来获得可用优化的完整列表和描述。

这些就是要对编译器选项执行的操作。如果您对这些并不熟悉,那么您会很高兴地了解到,您可以将它放置在 ~/.bashrc 文件中,并且在默认情况下使用这些选项。

我需要做的下一件事情是,使用希望我的 SSH 编译中包含的选项来配置编译我的机器。 我可以通过键入以下代码查看这些选项:

me@mymachine:~/openssh-3.7p1> ./configure --help

我可以根据自己的需要使用这些选项中的一些或全部,不过,默认的那些选项就可以满足我的需要,所以,我只需运行 configure 选项即可:

me@mymachine:~/openssh-3.7p1> ./configure

现在我需要做的就是编译源代码,使用 make 命令就可以轻松做到这一点:

me@mymachine:~/openssh-3.7p1> make

此刻我可以去喝一杯咖啡,因为该编译过程可能要花费一段时间。一旦完成该过程,我将得到我所需要的 OpenSSH 的所有部分, 而且由此产生的 OpenSSH 二进制文件已经完成了我给编译器的所有优化。我为该编译过程进行了计时,它用去了 2 分 25 秒的时间,这段时间 足够我去喝咖啡。

不过,我并不希望用这么长时间。我的机器被占用了 2 分 25 秒的时间,而这段时间内它可以做别的一些事情。两分钟看起来也许并 不长,但是 OpenSSH 是一个特别小的应用程序。如果是更大的程序,或者您每天都在进行开发,并且要多次编译代码,那么编译工作每天就会 消耗您几个小时的时间。我是一个忙人,没有那么多可以停工的时间。所以,由于心急,我开始使用 distcc。

distcc 是一个小型应用程序,它与 gcc 编译器是配合使用的,并且它允许在另一台安装了 distcc 的机器上进行编译工作。使用 distcc 的第一步是从 Web 站点(请参见 参考资料 来获得链接)将最新版本的 distcc 下载到您的工作台上。

如果正在使用 SuSE Linux,则可以从 SuSE 得到它们,或者从安装媒体中得到它们;如果正在使用 Gentoo Linux,则可以 运行 emerge distcc ,而 Debian 让我们运行 apt-get install distcc ,而且还提供了一个 FreeBSD 端口,如果您喜欢使用这种方式的话。

对于其他获得 .tar.gz 文件的人(或者那些喜欢 tarball 的人,比如我),可以执行以下操作:

me@mymachine:~/distcc-2.12.1> ./configure --with-gtk
me@mymachine:~/distcc-2.12.1> make

然后,您就成为了超级用户,接下来是安装:

me@mymachine:~/distcc-2.12.1> sudo make install

该过程将安装所有需要文件,distcc 新进程(distccd)应该在 /etc/init.d/distccd 目录中,在启动机器时可以自动启动该进程。如果没有正确将它链接到 rc.d 目录,您可以自己完成这项操作。

我们现在将手工启动这个新进程,而不是重新启动机器去启动它(毕竟,我们 正在 尽力节省我们的时间):

me@mymachine:~/distcc-2.12.1> sudo /etc/init.d/distccd start

值得注意的是,即使没有 root 权限,您也可以运行 distcc 新进程,这一点很棒。在任何启动 distcc 新进程 的机器上,它都只会在您的用户名下运行。

现在,只在一台机器上安装 distcc 是没有意义的;这不会给我们带来任何好处。我打算在我的局域网中找三位 正在使用 Linux 的朋友,看他们是否有兴趣安装 distcc,因为每一个安装 distcc 的人都会从“池(pool)”中受益。

值得注意的是,除了正在使用的 gcc 的版本,机器上通常不需要其他任何公用的东西:它们不需要共享文件系统、 头文件、库或者运行相同的 Linux 内核或发行版本。

这些完成这些操作之后,我需要告诉 distcc 它可以使用哪些机器。我们将这些机器命名为“film”、“flam”和“jabberwocky”。这一次的操作是设置环境变量 DISTCC_HOSTS(这个环境变量也可以放在 ~/.bashrc 中,以便永久使用),我用了 另一个导出操作来完成这项任务:

me@mymachine:~> export DISTCC_HOSTS="mymachine flim flam jabberwocky"

不过,我的机器不如 flim 和 jabberwocky 快,所以我将把它们移到了列表的前面。distccd 的工作原则好像是先到先工作。

me@mymachine:~> export DISTCC_HOSTS="flim jabberwocky mymachine flam"

现在我们应该全部都安排好了。让我们重新对 OpenSSH 进行编译,观察它在三台机器上而不是只在一台机器上运行时的时间花费情况:

me@mymachine:~> cd openssh-3.7p1

因为我们已经为 CFLAGSCXXFLAGSDISTCC_HOSTS 导出了环境变量 ,所以 我们可以不管不问地继续我们的操作,机器应该会记住我们的设置,除非我们将这些设置放在 ~/.bashrc 中,在这种情况下 它们将自动运行。

现在我们将清除以前的 make 结果,来获得一个干净的环境:

me@mymachine:~/openssh-3.7p1> make clean

开始之前还有另外一件事。distcc 程序自带了一个监视器,通过它我们可以观察哪台机器上正在编译哪个源文件。由 于我们在编译 distcc 时使用了 --use-gtk 选项,所以我们会有两个选择:distccmon-text 和 distccmon-gnome。现在我们将坚持使用控制台版本。启动一个新的终端会话,然后运行:

me@mymachine:~/openssh-3.7p1> distccmon-text 2

您会得到两秒一次的更新。另一种方法(我首选的方法):

me@mymachine:~/openssh-3.7p1> watch distccmon-text

这两种方法或多或少都达到了相同的目的:每两秒钟向您展示一个分布式编译的快照。我们已经完成这项任务, 现在可以运行 configure 。我们需要给 configure 发送一个选项,让它就知道不 要使用普通的 gcc,在没有任何其他指示的情况下,Linux 系统会默认使用它。

me@mymachine:~/openssh-3.7p1> CC=distcc ./configure

当在其他机器上完成配置的一个或两个部分时,distcc 监视器可能会发出‘blip’信号,并伴随少许活动。 完成配置之后,我们就准备好进行实际的编译了:

me@mymachine:~/openssh-3.7p1> make -j 12

我已经将 -j 选项传递给了 make。这不是一个 distcc 特定选项, -j 标志告诉 gcc 一次需要编译多少内容。完全有可能在没有运行 distcc 的机器上 使用 -j 来运行 make,在单 CPU 机器上将 -j 设置为 2 有 时会加快运行速度(但不明显)。不过,我们指定的是 12,表明如果有可能的话,我们将一次同时编译 12 个源文件。

让我们来看一看 distcc 监视器,看它正在做什么:

清单 1. distcc 命令行监视器
  5366  Preprocess  serve.c                       flim[0]
  5338  Compile     minilzo.c                     flim[1]
  5363  Preprocess  prefork.c                     flim[2]
  5360  Compile     ncpus.c                jabberwocky[0]
  5352  Compile     dparent.c              jabberwocky[1]
  5356  Compile     dsignal.c              jabberwocky[2]
  5349  Compile     dopt.c                   mymachine[0]
  5279  Compile     trace.c                  mymachine[1]
  5375  Preprocess  srvnet.c                 mymachine[2]
  5342  Compile     access.c                      flam[0]
  5346  Compile     daemon.c                      flam[1]
  5371  Preprocess  setuid.c                      flam[2]

使用 distcc 监视器,我们可以观察到哪个节点上正在编译哪个文件。右边节点名后面的数字指出了它是第n个当前编译文件。在这里,由于我们有四个节点,而且指定 -j 为 12, 所以在每台机器上有三个文件在进行编译。这很有意义,因为在移动所需文件时会有一些网络开销,如果每个节点上 只有一个编译(即 -j 4 ),那么 CPU 将会有很多闲置时间。

通过对这些机器进行计时,我得知编译时间缩短了 9 秒,速度提高了大约 16 倍。使用 distcc 进行编译,可以让我利用那些显然比 我的节点快得多的节点,同时还可以让我得到为我个人的工作站而编译并优化的应用程序。

为了观察不同的 -j 值带来的效果,让我们试着改变这些值并重新进行编译:

清单 2. 同步编译数量对编译时间的影响
     -j 值                                  编译时间(秒)
     4                                           19.5
     8                                           10.5
     12                                          8.9
     16                                          8.5
     20                                          8.6

我们可以看出,通过改变 -j 的值,可以获得某些好处,不同的配置会 产生不同的结果,但可能还是值得去实验的。另一点需要注意的是,如果您的 distcc 集群中有较多的机器,那么 将您的本地机器从列表中删除会有好处,因为您的机器可能会因为忙于将不同源文件分派到各个机器,同时还要接收编译对象文件而无法负担编译工作。确实如此,将您的本地机器留在列表中可能会使编译过程变慢。

关于版本还有最后一个注意事项。如果能保持对 distcc 集群中的所有节点使用相同副版本号的 gcc,则 distcc 程序可以获得最佳工作状态;如果这些节点使用的是不同副版本号,则可能会导致编译不稳定,甚至可能导致编译过程完全失败,因为已经修改的部分 gcc 足以产生这样的后果 。例如,如果上面例子中的 mymachine、flim 和 jabberwocky 运行的是 gcc 3.3.1,而 flam 运行的是 gcc 3.2.2,那么 OpenSSH 的编译可能会成功完成,或者可能失败,这取决于在哪台机器上编译哪个部分。要小心, 在这种情况下,即使是成功的编译也可能得不到期望的功能。

最佳策略是坚持使用副版本号相同的 gcc(例如,gcc 3.3.4 和 gcc 3.3.1 都是 gcc 3.3.x,所以它们可以一起用),这样就可以 都又正确又稳定地完成所有编译工作,如果编译失败,也不可能是与 distcc 有关。

那么,我们来总结一下这个过程:

  • 在所有您想用来编译的机器上安装 distcc
  • 启动每台机器上的 distcc 新进程
  • 用变量名导出 DISTCC_HOSTS 环境变量
  • 启动 distcc 监视器(这样就可以观察正在发生什么事!)
  • 使用 CC=distcc ./configure ,而不是使用 ./configure 来进行配置
  • 使用 make -j n ,而不是使用 makemake -j 2 来进行编译,其中,n 是 DISTCC_HOSTS中机器数目的两到三倍

如果您有从优化中受益的程序,那么从源代码中“构造您自己的(rolling your own)”二进制则是您应该选择的方式。 当进行大规模编译时,就所需要的最大执行时间(wall-clock time)而言,这样做的代价可能会比较高,而使用 distcc 能让您(和所有 其他人)充分利用网络上的闲置 CPU 周期,尽可能快地建立和运行您的程序。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=21634
ArticleTitle=使用 distcc 缩短编译时间
publish-date=07132004