利用pre-compiled headers技术以加速编译速度(一)
者一开始先使用build指令测试编译效能,然后关掉Project,删掉编译过程中产生的.obj 、 .tds 、 .exe档,然后重新开启Project,再使用make指令测试编译效能,此时,由于之前build时所产生的vcl50.#00以及vcl50.csm这两个档案依旧留在硬盘中,所以make时编译器就直接拿来用啦! 也因此我们看到编译器只编译了17行就结束。由此我们更可以证明, vcl50.#00以及vcl50.csm这两个档案就是我们所谓的cache档,而它们的作用就是让编译器可以减少编译的标头文件数目以加速编译。
由以上所得到的结论告诉我们,如果接下来我们要做pre-compiled headers技术对编译效能所产生影响之编译效能评估,应该在第一次编译的时候使用build指令,第二次以后都使用make指令,这样才能精确地测出pre-compiled headers技术对编译效能所带来的改善,因为从数据中我们可以看出,build指令会让编译器从头到尾重新编译一次,所以只看build之后产生的结果是没有意义的。
不过,有时候重头到尾重新编译整个系统也是在所难免。比方说我们一旦把程序从debug版本变成release版本,或是把程序从release版本变成debug版本,之后的第一次编译,即使我们使用make来编译程序,编译器所花的时间和使用build来编译的结果是一样的,都是重头到尾重新编译一次。我们还是可以利用pre-compiled headers技术让这种从头到尾的编译可以更快,在本篇文章的后面会提到。
<pre-compiled headers技术对编译速度的影响 -2>
前一段里面的测试程序只有一个单一的程序原始文件,接着我们来试试如果Project里面有多个程序原始文件的时候会有何种情形。为了避免情况复杂,我们只测试Project里头有两个程序原始文件的情况。首先,请使用 File/New新增一个Unit:
并将档案存成Unit2.cpp,此时我们Project之中就多出了两个档案,分别是Unit2.h以及Unit2.cpp,他们的内容如下:
程序代码3:
Unit2.h
#ifndef Unit2H
#define Unit2H
void test(void) ;
#endif
Unit2.cpp
#include <vcl.h>
#include <stdio.h>
#pragma hdrstop
#include "Unit2.h"
void test(void)
{
printf("test") ;
}
测试结果4:
编译次数 编译行数 编译时间
第一次(build) 375564 10.03
第二次(make) 0 0.16
我们观察BCB所在目录之下的Lib目录,此时会发现之前的测试只有多两个档案,而这次的测试竟然又多了一个档案,他们分别是: vcl50.csm、vcl50.#00、vcl50.#01。
这样的测试结果似乎没有什幺结论,所以我们在第二次编译之后,第三次编译之前,把Unit2.cpp的内容修改如下:
程序代码4:
#include <vcl.h>
#include <stdio.h>
#pragma hdrstop
#include "Unit2.h"
void test(void)
{
printf("test") ;
printf("test1") ;
}
则测试结果变成:
测试结果5(接测试结果4):
编译次数 编译行数 编译时间
第三次(make) 30 1.19
这跟我们预期的结果相同,pre-compiled headers技术完全发挥了缩短编译时间的功能。
后来笔者在Project多加了几个Unit来测试,证明BCB除了会产生vcl50.csm之外,他会为每个Unit都产生一个vcl50.#??的档案(这里的前提是:每个Unit所引入的标头档彼此都不同,如果相同的话,会有另外一种情况发生),如果我们有三个Unit,他就会在第一次build的时候产生vcl50.#00、vcl50.#01、vcl50.#02,然后加上原本一定会产生的vcl50.csm,总共就会有4个cache档案,因此我们几乎可以认定『编译器会为每一个使用编译器指令#pragma hdrstop的档案产生一个cache文件,以加速编译』这个我们所见的事实。也就是说,如果我们把Unit2.cpp里头的编译器指令#pragma hdrstop拿掉,那幺每次我们修改Unit2.cpp之后所测得的结果应该是:
测试结果6:
编译次数 编译行数 编译时间
第三次(make) 173843 2.69
而不是前面测试结果5的测试结果。因为没有预先编译好的cache檔,所以Unit2.cpp在修改程序后,必须从头到尾从新编译。
笔者的另外一个测试,是先在Unit2.cpp中使用编译器指令#pragma hdrstop,然后用make编译执行档,让编译器帮我们产生Unit2.cpp的cache档,笔者并没有删除任何的vcl50.#??档案。接着我将Unit2.cpp里头的编译器指令#pragma hdrstop拿掉,虽然每次原始码的更改都会造成编译行数多达173843上下,可是在make几次之后,如果笔者重新将编译器指令#pragma hdrstop放回Unit2.cpp之中原来的位置(也就是在#include <vcl.h>与#include <stdio.h>之下),则每次修改程序之后编译的结果会比较接近测试结果5。
在此我们暂且先把编译器指令#pragma hdrstop之前所有引入的标头档档名所构成的集合称做『预先编译标记』,我们大胆假设编译器会帮我们把这个标记记录在cache文件里头,以方便下次编译器在编译其它档案的时候作为辨识用。
è我们根据上面的假设归纳了一个暂时的结论,就是:编译器每次在编译程序原始码的时候,一般都是重新编译所有的标头档。但是,如果程序原始文件中含有编译器指令#pragma hdrstop,那幺编译器就会去寻找Lib目录底下的vcl50.#??,看看这些档案是否符合目前的『预先编译标记』,如果符合,那幺编译器就直接引用之前编译后留下的cache,因而省下许多重新编译标头档的时间,如果没有任何cache文件一个符合标记,那幺编译器仍然会重头开始编译所有的标头档,并在编译后自己产生一个和这个程序原始文件有相同『预先编译标记』的cache文件,待下次有程序原始文件的预先编译标记和这个cache文件的预先编译标记相同时,编译器就会直接引用这个cache檔。
由以上所得到的结论告诉我们,如果接下来我们要做pre-compiled headers技术对编译效能所产生影响之编译效能评估,应该在第一次编译的时候使用build指令,第二次以后都使用make指令,这样才能精确地测出pre-compiled headers技术对编译效能所带来的改善,因为从数据中我们可以看出,build指令会让编译器从头到尾重新编译一次,所以只看build之后产生的结果是没有意义的。
不过,有时候重头到尾重新编译整个系统也是在所难免。比方说我们一旦把程序从debug版本变成release版本,或是把程序从release版本变成debug版本,之后的第一次编译,即使我们使用make来编译程序,编译器所花的时间和使用build来编译的结果是一样的,都是重头到尾重新编译一次。我们还是可以利用pre-compiled headers技术让这种从头到尾的编译可以更快,在本篇文章的后面会提到。
<pre-compiled headers技术对编译速度的影响 -2>
前一段里面的测试程序只有一个单一的程序原始文件,接着我们来试试如果Project里面有多个程序原始文件的时候会有何种情形。为了避免情况复杂,我们只测试Project里头有两个程序原始文件的情况。首先,请使用 File/New新增一个Unit:
并将档案存成Unit2.cpp,此时我们Project之中就多出了两个档案,分别是Unit2.h以及Unit2.cpp,他们的内容如下:
程序代码3:
Unit2.h
#ifndef Unit2H
#define Unit2H
void test(void) ;
#endif
Unit2.cpp
#include <vcl.h>
#include <stdio.h>
#pragma hdrstop
#include "Unit2.h"
void test(void)
{
printf("test") ;
}
测试结果4:
编译次数 编译行数 编译时间
第一次(build) 375564 10.03
第二次(make) 0 0.16
我们观察BCB所在目录之下的Lib目录,此时会发现之前的测试只有多两个档案,而这次的测试竟然又多了一个档案,他们分别是: vcl50.csm、vcl50.#00、vcl50.#01。
这样的测试结果似乎没有什幺结论,所以我们在第二次编译之后,第三次编译之前,把Unit2.cpp的内容修改如下:
程序代码4:
#include <vcl.h>
#include <stdio.h>
#pragma hdrstop
#include "Unit2.h"
void test(void)
{
printf("test") ;
printf("test1") ;
}
则测试结果变成:
测试结果5(接测试结果4):
编译次数 编译行数 编译时间
第三次(make) 30 1.19
这跟我们预期的结果相同,pre-compiled headers技术完全发挥了缩短编译时间的功能。
后来笔者在Project多加了几个Unit来测试,证明BCB除了会产生vcl50.csm之外,他会为每个Unit都产生一个vcl50.#??的档案(这里的前提是:每个Unit所引入的标头档彼此都不同,如果相同的话,会有另外一种情况发生),如果我们有三个Unit,他就会在第一次build的时候产生vcl50.#00、vcl50.#01、vcl50.#02,然后加上原本一定会产生的vcl50.csm,总共就会有4个cache档案,因此我们几乎可以认定『编译器会为每一个使用编译器指令#pragma hdrstop的档案产生一个cache文件,以加速编译』这个我们所见的事实。也就是说,如果我们把Unit2.cpp里头的编译器指令#pragma hdrstop拿掉,那幺每次我们修改Unit2.cpp之后所测得的结果应该是:
测试结果6:
编译次数 编译行数 编译时间
第三次(make) 173843 2.69
而不是前面测试结果5的测试结果。因为没有预先编译好的cache檔,所以Unit2.cpp在修改程序后,必须从头到尾从新编译。
笔者的另外一个测试,是先在Unit2.cpp中使用编译器指令#pragma hdrstop,然后用make编译执行档,让编译器帮我们产生Unit2.cpp的cache档,笔者并没有删除任何的vcl50.#??档案。接着我将Unit2.cpp里头的编译器指令#pragma hdrstop拿掉,虽然每次原始码的更改都会造成编译行数多达173843上下,可是在make几次之后,如果笔者重新将编译器指令#pragma hdrstop放回Unit2.cpp之中原来的位置(也就是在#include <vcl.h>与#include <stdio.h>之下),则每次修改程序之后编译的结果会比较接近测试结果5。
在此我们暂且先把编译器指令#pragma hdrstop之前所有引入的标头档档名所构成的集合称做『预先编译标记』,我们大胆假设编译器会帮我们把这个标记记录在cache文件里头,以方便下次编译器在编译其它档案的时候作为辨识用。
è我们根据上面的假设归纳了一个暂时的结论,就是:编译器每次在编译程序原始码的时候,一般都是重新编译所有的标头档。但是,如果程序原始文件中含有编译器指令#pragma hdrstop,那幺编译器就会去寻找Lib目录底下的vcl50.#??,看看这些档案是否符合目前的『预先编译标记』,如果符合,那幺编译器就直接引用之前编译后留下的cache,因而省下许多重新编译标头档的时间,如果没有任何cache文件一个符合标记,那幺编译器仍然会重头开始编译所有的标头档,并在编译后自己产生一个和这个程序原始文件有相同『预先编译标记』的cache文件,待下次有程序原始文件的预先编译标记和这个cache文件的预先编译标记相同时,编译器就会直接引用这个cache檔。

