在标准的编译过程中,在 UNIX 下使用 C/C++ 开发应用程序通常需要用到一个编译器(如 gcc)以及一个编译工具,比如 make。make 和所有的 C 编译器的问题在于 C 预处理程序(preprocessor)和头文件的工作方式。观察一个典型的 C 源文件,您会发现其中有很多由 #include 所引用的各种头文件。
每一次编译一个文件时,C 预处理程序(cpp)都会解析并引入每个头文件以及这些头文件引用到的任何文件。通过对内容进行解析,ccp 可以将一个相当基本的 1-KB 大小的源文件转化为一个 8-KB 大小的源文件,在这个过程中,会合并入几十个甚至几百个头文件。在典型的开发项目中,有很多与项目相关的头文件可能会在不同的源文件中多次被引入,而且每个头文件本身也可能引用很多其他头文件。
在典型的编译过程中,make 工具只编译自上次编译后发生修改的文件,这样就在很大程度上简化了编译过程。例如,清单 1 中的目录表明, foo.o 对象比相应的 foo.c 源文件的最后修改日期更新。同时,bar.o 比 bar.c 更新。使用一个经过适当配置的 Makefile,将只会从源文件编译 foo.o 。
make 将必须被编译的文件限制在经过修改的那些源文件范围之内,但是即使是使用 make,仍然有相当可观的浪费。每一次编译项目时,源文件在编译为汇编语言和最终的机器代码之前,都要通过 cpp 进行解析。对每一个文件来说,每一次可能都要重新解析头文件。从编译的全过程来看,您最后可能多次解析了相同的头文件,浪费了处理器周期,更重要的是浪费了开发者的时间,因为他们要等待这一过程的完成。在一个团队中,这一影响可能会更为明显,因为多名开发者可能都会反复多次重复这一过程,在某一天甚至可能会同时进行。
清单 1. 一个示例源文件环境1
2
3
4
5
6
7
| total 808
-rw------- 1 mc mc 5123 24 Jul 14:17 bar.c
-rw------- 1 mc mc 39474 24 Jul 14:19 bar.o
-rw------- 1 mc mc 7856 24 Jul 14:17 foo.c
-rw------- 1 mc mc 28443 24 Jul 14:19 foo.o
-rwx--x--x 1 mc mc 319742 24 Jul 14:19 foobar*
-rw------- 1 mc mc 1045 24 Jul 14:21 foobar.h
|
使用 ccacheccache(“compiler cache”的缩写)工具会高速缓存编译生成的信息,并在编译的特定部分使用高速缓存的信息,比如头文件,这样就节省了通常使用 cpp 解析这些信息所需要的时间。如果您编译清单 2 中的文件,假定 foobar.h 中包含对其他头文件的引用,ccache 会用那个文件的 cpp-parsed 版本来取代 include 声明。就那么简单。不是真正去读取、理解并解释其内容,ccache 只是将最终的文本拷贝到文件中,使得它可以立即被编译。
清单 2. 源文件内容1
2
3
4
| #include "foobar.h"
void main(void)
{
}
|
安装安装和使用 ccache 并不像您可能想像的那样复杂。它不会取代或者以任何方式影响您原来的使用编译器的方式,而是担当了您与您的编译器之间的一个接口,所以您可以根据需要选择是否使用它。要安装 ccache,需要从 Samba 小组或者一个本地镜像(参阅本文最后的 )直接下载源文件。解压出文件的内容:
$ bunzip2 -c ccache-2.3.tar.bz2|tar xf -
切换到那个目录:
$ cd ccache-2.3
配置:
$ ./configure
编译:
$ make
最后,安装 ccache:
$ make install
您就准备好开始使用了!
部署如上所述,ccache 在您与您的普通编译器之间进行工作。以 gcc 为第一个参数调用 ccache,而不是调用 gcc。例如,要在命令行中编译一个文件,您通常会使用:
$ gcc foo.c
要使用 ccache,您应该输入:
$ ccache gcc foo.c
像这样对一个文件进行单独的编译,尤其是第一次使用 ccache 编译那个文件时,您将不会看到有任何的帮助,因为编译信息还没有被高速缓存。所以,配置 ccache 永久地取代主要编译器通常来说更为有效。设置 CC 环境变量的值来完成这一任务:
$ export set CC='ccache gcc'
如果您只是想为一个项目启用 ccache,比如说编译 Perl 等第三方工具时,那么您或者可以使用环境变量,或者可以告知配置脚本或 make 命令使用哪个 C 编译器。
控制高速缓存默认情况下,ccache 使用当前用户主目录中的一个目录($HOME/.ccache)来保持高速缓存信息。在团队环境中,您应该使用一个集中的位置来进行高速缓存,这样在编译过程中每个人都可以使用高速缓存的信息。另一个环境变量 CCACHE_DIR 指定了高速缓存目录的位置。在单机环境中,将这个环境变量设置为一个每一个需要它的人都可以访问的目录。使用通过 tmpfs 挂载的目录可以获得更高的速度,前提是您得有支持这一功能的存储器。您的速度可能会再提高 10% 到 25%。
如果您是在网络中多台机器上使用 ccache,那么要确保您共享的目录要通过 NFS 导出(export)并挂载到每一个客户机上。如果您希望获得额外的加速,同样可以使用 tmpfs 文件系统。
另外的一些选项让您可以更深入地控制高速缓存设置:
- CCACHE_LOGFILE 环境变量定义了使用高速缓存时生成的日志文件所处的位置。
- 在 ccache 中使用 -s 命令行选项来获得关于高速缓存性能的统计数据(见清单 3)。
- 使用 -M 命令行选项来设置高速缓存的最大大小。默认是 1GB。高速缓存的设置会写入到高速缓存目录,所以您可以让不同的用户和组在不同的位置拥有不同大小的高速缓存。
- -F 选项设置高速缓存目录的最大文件数目,按 16 进制舍入。和 -M 相同,只是当您希望改变配置的时候才需要使用它。
- -c 选项清空缓存。您通常不需要使用这个选项,因为 ccache 在执行过程中会更新信息,但是,如果您要重用一个没有为某个文件所使用的高速缓存目录,那么就应该尝试使用这个选项。
- -C 选项完全清空高速缓存。
清单 3. ccache 高速缓存统计数据1
2
3
4
5
6
7
8
| cache hit 44
cache miss 152
called for link 107
compile failed 11
no input file 2
files in cache 304
cache size 8.8 MB
max cache size 976.6 MB
|
一旦设置了初始化选项并配置了期望的目录和高速缓存大小,就不需要再做任何改动。没有必要执行任何日常的维护。
组合 ccache 和 distcc您可能已经想到了 distcc 这一来自 Samba 小组的另一个工具,它让您可以将编译过程分布到多台机器上。只需要为 make 添加多任务选项(使用 -j 命令行选项),它就可以有效地提高同步编译的数目。distcc 系统的工作方式是,每台主机上有一个后台进程,接收最终预解析格式的源文件,然后在本地进行编译,返回生成的对象文件。
如果使用得当,在每加入一个新的同样节点时,编译时间通常应该会以稍微低于线性的比率下降,不过您将只会在那些远不只一个源文件的项目上看到这样的影响,因为 distcc 只是分布全部源文件。
由于 distcc 所分布的是解析过的文件,所以您可以组合 ccache,它可以加速 C 预处理过程部分,同时distcc 可以完成到对象代码的实际编译。要以这种方式来使用 distcc 和 ccache,需要在主机上配置 distcc,在主要的开发机器上配置 distcc 和 ccache。
现在在希望要编译项目的机器上设置环境变量,如清单 4 所示。
清单 4. 使用 ccache 和 distcc 所需要的环境变量1
2
3
4
5
| export set DISTCC_HOSTS='localhost atuin nautilus pteppic kernel'
export set CCACHE_DIR=/Data/Cache/CCache
export set CCACHE_PREFIX=distcc
export set CCACHE_LOGFILE=/Data/Cache/CCache.log
export set CC='ccache gcc'
|
环境变量定义如下:
- DISTCC_HOSTS 指定了将工作分布到哪些主机。
- CCACHE_DIR 指定了高速缓存目录的位置。
- CCACHE_PREFIX 定义了当 ccache 调用真实的编译器来编译源文件(预处理之后)时所使用的前缀。
- CC 设置首先使用的 C 编译器的名称(ccache)。
现在,当运行 make 时,如果使用了 -j 选项来指定要执行的同步编译的数目,则首先使用 ccache 解析文件(如果需要,使用高速缓存),然后将其分布到某个 distcc 主机。
尽管 distcc 加速了编译过程,但是它没有改变环境的基本限制。例如,您不应该将 make 执行的同步作业的数目设置得大于可用 CPU 数目的两倍。例如,如果您有四台两路机器,那么将作业值设置为超过 16 的值时将不再会观察到有多大改善。
统计数据既然一切都已就绪,现在可以观察它带来了多大的差别。在这里,我已经运行了一系列编译 Perl 的测试。我们需要编译一个足够大的项目,因为 ccache 在高速缓存了解析过的头文件时运转效果最好。这正是在完成了标准配置(使用 configure.gnu)以后的 make 阶段,它包括所有的步骤,甚至那些与编译代码无关的步骤。这些与编译器无关的操作不会影响整体上的统计数据。
如前所述,在第一次编译时,ccache 的影响不会为人所察觉。不同之处在于重新编译时,在这个过程中您会重用先前的预处理程序。通过简单地接触(touch)主要 Perl 源文件目录中的 C 源文件,得到了表 1 中的重新编译时间。其中包括使用普通 gcc、ccache+gcc、ccache+distcc+gcc 所需的编译时间,编译在一个四节点网络中进行并选用了不同的并发 distcc 作业数目值。
表 1. 重新编译时间环境
时间gcc(第一次运行)
8m02.273sgcc(重新编译)
3m30.051sccache+gcc(第一次运行)
8m54.714sccache+gcc(重新编译)
0m45.455sccache+distcc+gcc -j4
4m14.546sccache+distcc+gcc -j4(重新编译)
0m38.985sccache+distcc+gcc -j8
3m13.020sccache+distcc+gcc -j8(重新编译)
0m34.380s
哇!只是使用了 ccache,编译 Perl 就节省几乎 3 分钟(2 分 45 秒)的时间,所有的原因只是,ccache已经保持了头文件的预编译版本,因而不需要再为每个源文件不断重复运行 cpp。将 distcc 引入这一过程所得的结果是,整体上速度提高,重新编译的时间快了一点点。
结束语在本文中,您已经了解了通过 ccache 等相当直观而且易用的工具可以获得怎样的速度上的提高。组合使用 ccache和 distcc,您可以更进一步地改进编译时间。在团队环境中使用这些工具时,您每天就可以节省几个小时的编译时间。这就意味您的开发职员喝咖啡休息的理由会更少,但是也会减少应用程序的开发时间。 |