程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 通過預編譯頭文件來提高CB的編譯速度

通過預編譯頭文件來提高CB的編譯速度

編輯:關於C++

C++ Builder是最快的C++編譯器之一,從編譯速度來說也可以說是最快的win32C++編譯器了。除了速度之外,C++builder的性能也在其它C++編譯器的之上,但許多delphi程序員仍受不了C++builder工程的編譯速度。的確,delphi的速度要比任和c++的編譯器都要快好多。Delphi在編譯一個小工程的時候可能不到一秒,大的工程一般也在5秒鐘這內編譯完成了。

為什麼delphi會比c++builder快這麼多?是否有方法來c++builder的編譯速度?本文就講解了為什麼C++的編譯器速度會慢,並且介紹了一個簡單的方法來減少c++builder的編譯時間。

為什麼c++編譯器的速度會慢?

c++builder 使用者怎麼通過預編譯頭文件來減少編譯時間?

講解基於VCL可視化工程的預編譯頭文件方法

優化c++builder對預編譯頭文件的使用

結論

注意事項

為什麼c++編譯器速度慢?

在C++中,你只能使用預定義或是預先聲明了的函數,這意味什麼?來看一個簡單的例子,函數A()調用函數B(),函數A()只能在函數B()的原型或是函數體在A()之前才能調用它。下面的例子說明了這一點:

// declaration or prototype for B
void B();
void A()
{
   B();
}
// definition, or function body of B
void B()
{
   cout << "hello";
}

沒有B()的原型,這個代碼不會編譯通過的,除非函數B()的函數體移到函數A()之前。

對於編譯器來說,函數的原型很重要。當你運行程序時,編譯器都要插入恰當的代碼來調用程序。編譯器必需知道要有多少個參數傳給函數。也要知道函數的參數應該在棧裡還是在寄存器裡。總而言這,編譯器必需知道怎麼來產生正確的代碼來調用這個函數,這就要求編譯器必需知道預先聲明或定義了的被調用的函數。

為使函數或類的原型簡單化,C++提供了一個#include 指令。#include代表允許源文件在函數原型被調用的位置之前包含的一個頭文件中找到函數原型。#include 指令在win32C++編程中很重要。C RTL函數的原型都包含在標准的頭文件集中。win32API的原型全在微軟提供的頭文件集中,VCL中的類和函數的在原型則在隨C++builder發行的頭文件中。沒有這些,你幾乎做不了什麼。

頭文件提供了一種讓程序員很容易管理的方式來執行C++的類型檢查,但是也帶來了很大的代價。當編譯器運行到一個#include 指令時,它會打開這個頭文件並插入到當前文件中,然後編譯器象分析已編譯的文件一樣來分析這些包含進來的文件。當被包含的文件中還包含有其它的頭文件時會怎麼樣呢?編譯器仍會插入那個文件再分析它,想象一下,當10、20甚至100個文件被包含時呢?盡管如此數量的包含文件聽起來很多,但當你加入window sdk頭文件和所有vcl頭文件時,這並不是不可能的。

來舉個例子說明一下編譯器是如何展開和翻譯被包含的文件的。這是一個我用console wizard建立的一個簡單的控制台程序。為了試驗代碼,在options-project-compiler在把pre-compiled headers選項關掉。

// include some standard header files
//包含了一些標准的頭文件
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <windows.h>
#pragma hdrstop
#include <condefs.h>
//-----------------------------------------------
int main()
{
   printf("Hello from printf.\n");
   cout << "Hello from cout" << endl;
   MessageBeep(0);
   return 0;
}

當用c++builder編譯工程時,編譯進度對話框顯示工程中包含有130,000行代碼。13萬行!怎麼回事?源文件中只有大約四行代碼,這13萬行都包含在stdio.h,string.h,iostream.h,windows.h和被這四個頭文件所包含的頭文件裡。

好,現在來看一下當工程中包含多個cpp文件時情況是怎麼樣的。停止編譯這個工程,再加一個已有的文件到這個工程中。在第二個文件中加一個簡單的函數。再到第一個文件中來調用這個新函數。

//-------------------------------------------------------
// UNIT1.CPP
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <windows.h>
#include "Unit1.h"    // prototype A() in unit1.h
#pragma hdrstop
void A()
{
   printf("Hello from function A.\n");
}
//-------------------------------------------------------
//-------------------------------------------------------
// PROJECT1.cpp
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <windows.h>
#include "Unit1.h"
#pragma hdrstop
#include <condefs.h>
//-------------------------------------------------------
USEUNIT("Unit1.cpp");
//-------------------------------------------------------
int main()
{
   printf("Hello from printf.\n");
   cout << "Hello from cout" << endl;
   A();
   MessageBeep(0);
   return 0;
}
//-------------------------------------------------------

好,現在再來編譯這個工程。如果在編譯之前你關掉了pre-compiled頭文件選項,當你編譯完時你會發現編譯進度對話框顯示共有260,000行代碼。可以看到,編譯器不得不把兩個文件都包含的相同的頭文件集都做處理。在前面的例子裡,編譯器多處理了這些頭文件帶來的13萬行代碼。第二個文件又讓編譯器處理了同樣的13萬行代碼,總共26萬行。不難想象在一個大工程裡行數將會以什麼速度增長。一遍又一遍的處理這些相同的頭文件大大的增加了編譯的時間。

C++Builder是如何通過預編譯頭文件的方法來減少編譯時間的

Borland的工程師認識到可以設計一個不用一遍又一遍處理同樣的頭文件的編譯器來減少編譯時間。於是Borland c++3.0中引入了預編譯頭文件的概念。處理方法很簡單,當編譯器處理源文件中的一組頭文件時,把編譯好的映象文件保存在磁盤上,當其它的源文件是引用同樣的一組頭文件時編譯器直接讀取編譯好的文件而不是再一次分析。

修改一下剛才的控制台程序來看看預編譯頭文件是如何減少編譯時間的。代碼本身不用修改,僅僅把工程選項中的預編譯頭文件選項再選中就行了。選擇Options-Project-Compiler並選中Use pre-compiled headers或是Cache pre-compiled headers。在預編譯頭文件名一欄中填入PCH.CSM。改好之後從重編譯工程。

編譯過程中注意一下編譯進度對話框。你會發現當編譯器編譯project1.cpp時要處理130,000行代碼,而編譯UNIT1.cpp時只處理20行代碼。當編譯器分析第一個源文件時產生了一個預見編譯頭文件的映象,在處理第二個文件時直接用來提高編譯速度。可以想象當源文件的數目不是2而是50時,性能會提高多少!

VCL GUI工程中預編譯頭文件的說明

通過預編譯頭文件的方法,上一個示例起碼減少了50%的編譯時間。而這不過僅僅是一個沒什麼功能的簡單的控制台程序而已。你也會到在VCLGUI程序中會怎麼樣呢?缺省情況下,c++builder自動打開工程的預編譯頭文件選項的。但它僅僅對vcl.h文件進行預處理,這個頭文件可以在任何一個窗體的源文件頂端找到。

#include <vcl.h>

#pragma hdrstop

#pragma hdrstop指令通知編譯器停止產生預編譯映象。在hdrstop指令之前的#include語句會被預編譯,之後的就不會了。

那當vcl.h被預編譯時到底有多少頭文件也被預編譯了呢?可以查看一下vcl.h,看到它包含了另一個叫做vcl0.h的頭文件。如果你沒有更改C++builder的缺省設置,vcl0.h就包含了一小組vcl的頭文件,它們是:

// Core (minimal) VCL headers
//
#include <sysdefs.h>
#include <system.hpp>
#include <windows.hpp>
#include <messages.hpp>
#include <sysutils.hpp>
#include <classes.hpp>
#include <graphics.hpp>
#include <controls.hpp>
#include <forms.hpp>
#include <dialogs.hpp >
#include <stdctrls.hpp>
#include <extctrls.hpp>

這是一小部分常被重復包含的頭文件,也許它只是大中型工程常用到的頭文件的一個了集。vcl0.h還允許你通過定義條件(#define)來預編譯更多的頭文件。你可以通過#define一個叫做INC_VCLDB_HEADERS的變量來預編譯數據庫的頭文件。同樣,還可以定義INC_VCLEXT_HEADERS來預編譯c++builder的擴展控件(Extra controls)。如果你還定義了INC_OLE_HEADERS,C++builder還會預編譯一些SDK COM的頭文件。這些定義要放在#include vcl.h語句這前。

#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#include <vcl.h>
#pragma hdrstop

注意:如果你要使用這些功能的話,你要確保把這兩個#define加到每個cpp文件,即使是沒有用到數據庫或是擴展控件的文件。至於原因稍後會被講到。

使用預編譯頭文件來優化c++builder。

缺省的預編譯頭文件設置確實減少了編譯工程的時間。你可以通過打開和關閉觀預編譯頭文件選項時完全編譯一個大工程所要用的時間來證明這一點。本節的目的就是通過改善預編譯頭文件的方法來進一步減少編譯時間。這裡我要講到兩個技術。

在討論這兩個技術之前呢,有必要了解一下c++builder在編譯源程序時是怎樣來決定是否使用已存在的預編譯的頭文件的映象的。c++builder會給你工程中的每一個源文件都產生一個唯一的預編譯頭文件映象。這些映象被保存在硬盤上的一個文件裡。編譯器會在有兩個源文件使用同樣的預編譯頭文件映象時來重用一個映象文件。這是很重要的一點,兩個源文件包含了完全一樣的頭文件時就會使用預編譯頭文件映象。再有就是他們包含這些頭文件的順序必需相同。簡單的說:源文件的#pragma hdrstop指令之前必需完全相同。下面是一些例子:

示例1:預編譯頭文件映象不匹配

  //--------------------         //--------------------
   // UNIT1.CPP              // UNIT2.CPP
   #include <stdio.h>           #include <iostream.h>
   #pragma hdrstop            #pragma hdrstop

示例2:預編譯頭文件映象不匹配

  //--------------------         //--------------------
   // UNIT1.CPP              // UNIT2.CPP
   #include <stdio.h>           #include <stdio.h>
   #include <iostream.h>         #pragma hdrstop
   #pragma hdrstop

示例3:預編譯頭文件映象不匹配

  //--------------------         //--------------------
   // UNIT1.CPP              // UNIT2.CPP
   #include <stdio.h>           #pragma hdrstop 
   #pragma hdrstop            #include <stdio.h>

示例4:預編譯映象匹配

  //--------------------         //--------------------
   // UNIT1.CPP              // UNIT2.CPP
   #include <stdio.h>           #include <stdio.h>
   #include <string.h>          #include <string.h>
   #include <iostream.h>         #include <iostream.h>
   #include <windows.h>          #include <windows.h>
   #include "unit1.h"           #include "unit1.h"
   #pragma hdrstop            #pragma hdrstop

示例5:預編譯映象匹配

  //--------------------         //--------------------
   // UNIT1.CPP              // UNIT2.CPP
   #define INC_VCLDB_HEADERS       #define INC_VCLDB_HEADERS
   #define INC_VCLEXT_HEADERS      #define INC_VCLEXT_HEADERS
   #include <vcl.h>            #include <vcl.h>
   #pragma hdrstop            #pragma hdrstop
   #include "unit1.h"           #include "unit2.h"

示例6:預編譯映象不匹配

  //--------------------         //--------------------
   // UNIT1.CPP              // UNIT2.CPP
   #define INC_VCLDB_HEADERS       #include <vcl.h>
   #define INC_VCLEXT_HEADERS      #pragma hdrstop
   #include <vcl.h>
   #pragma hdrstop

當編譯器處理一個預編譯映象不匹配的源文件時就會重新再產生一個全新的映象。看上面的示例2。盡管stdio.h在unit1.cpp中已經被編譯過了,但是unit2中還是要被編譯的。只有在多個文件中都能用到預編譯映象編譯器才能減少編譯的時間。

這就是我所講到的技術的基礎。預編譯盡可能多的頭文件,並確保在每個源文件中都用到同樣的預編譯映象。

技術1:

第一個技術只是通過在每個源文件中定義兩個條件來增加vcl.h中包含的頭文件的數目。打開工程中的每個cpp文件包括工程文件,象下面看到的那樣改變它們的頭兩行。

#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#include <vcl.h>
#pragma hdrstop

如果你不喜歡這種方法,你可以在Project-Options-Directories/Conditional條件定義欄中填入INC_VCLDB_HEADERS 和 INC+VCLEXT_HEADERS來達到同樣的目的。

或許你也想插入一些windows.h之類的你常用的C RTL頭文件,要確保插入到hdrstop pragma之前,而且順序要相同。

#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#include <vcl.h>
#include <windows.h>
#include <stdio.h>
#pragma hdrstop

技術2:

技術1的效果很好,但它不是很靈活。如果你想要在觀感編譯的頭文件列表中再加入一個新的頭文件的話,你要修改你工程中的每一個源文件。此外,技術一容易出錯。如果你把包含的順序弄亂了,你只會更糟而不是更好。

技術二處理了技術一的一些缺點。方法就是創建一個巨大的頭文件來包含你工程中用到的所有的頭文件。這個頭文件將包含有VCL的頭文件、window SDK的頭文件以及RTL頭文件。當然你也可以包含一些創建的窗體的頭文件,但稍會你會了解到不應該把一些常修改的文件做預編譯處理(查看注意事項:不要預編譯變化的頭文件)

下面就樣一個文件的示例:

//---------------------------------------------------------
// PCH.H: Common header file
#ifndef PCH_H
#define PCH_H
// include every VCL header that we use
// could include vcl.h instead
#include <Buttons.hpp>
#include <Classes.hpp>
#include <ComCtrls.hpp>
#include <Controls.hpp>
#include <ExtCtrls.hpp>
#include <Forms.hpp>
#include <Graphics.hpp>
#include <ToolWin.hpp>
// include the C RTL headers that we use
#include <string.h>
#include <iostream.h>
#include <stdio.h>
// include headers for the 3rd party controls
// TurboPower System
#include "StBase.hpp"
#include "StVInfo.hpp"
// Our custom controls
#include "DBDatePicker.h"
#include "DBRuleCombo.h"
#include "DBPhonePanel.h"
// Object Repository header files
#include "BaseData.h"
#include "BASEDLG.h"
// project include files
// pre-compile these only if PRECOMPILE_ALL is defined
#ifdef PRECOMPILE_ALL

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved