加速Windows GUI debug版本的編譯shell
1. 問題描述
咱們重構咱們的GUI程序時,增長了不少小的工程庫,VC2008編譯GUI最頂層DLL庫libpkgA的速度讓人幾乎沒法忍受。 如下是從咱們的buildbot系統裏截取出來的LOG:api
28>...
28>Embedding manifest...
28>Build Time 188:17xcode
編譯時間188分鐘。app
GUI app依賴於這個DLL庫,同時也依賴其它一些庫,編譯它的LOG以下:flex
79>...
79>Embedding manifest...
79>Build Time 157:32ui
又須要157分鐘。google
若是算上編譯其它子庫的時間(~37mins),那麼編譯GUI app所須要的時間爲 382mins (6.4hours)。 我想,clean build DEBUG GUI app須要6.4個小時的時間幾乎是沒法忍受的。lua
咱們工程項目的庫依賴以下:
最頂層的DLL庫錯綜複雜的依賴下面全部的靜態庫。debug
如何加速這個編譯過程?code
面對如此狀況,咱們只能硬着頭皮上,嘗試着先把問題分析下,認識清楚咱們所面對的問題。 請注意,咱們VC版本是2008。
2. 問題分析
既然編譯APP和DLL狀況相似,如下就拿編譯GUI app時存在的問題來分析:
Creating command line """e:\GUIApp\Build-vc90\guiapp\Debug\BAT00001811168124.bat""" Creating temporary file "e:\GUIApp\Build-vc90\guiapp\Debug\RSP00001911168124.rsp" with contents [ /Od /I "D:\wxWidgets-2.8.9\src" /I "D:\wxWidgets-2.8.9\include" /I "D:\wxWidgets-2.8.9\contrib\include" /I "E:\GUIApp\third_party\wxWidgets-2.8.9\lib\vc_lib\mswd" /I "E:\GUIApp\third_party\propgrid\include" (more options) ] Creating command line "cl.exe @"e:\GUIApp\Build-vc90\guiapp\Debug\RSP00001911168124.rsp" /nologo /errorReport:prompt"
能夠看到VC會在編譯(compile,調用cl.exe)以前先推導出該庫所須要的編譯選項,保存到一個臨時文件中,以後以這個臨時文件做爲輸入啓動cl.exe編譯項目的代碼。
Creating temporary file "e:\GUIApp\Build-vc90\guiapp\Debug\RSP0000131924013376.rsp" with contents [ /OUT:"....\Build-vc90\bin\Debug\guiapp.exe" /INCREMENTAL:NO /LIBPATH:"E:\GUIApp\third_party\guiapp-Libs\vc90\Debug" /LIBPATH:"....\Build-vc90\bin\Debug" /LIBPATH:"E:\GUIApp\third_party\guiapp-Libs\vc90\DLL_Debug\wxlib28" /LIBPATH:"E:\GUIApp\third_party\BugTrap\Win32\Bin\" /LIBPATH:"E:\GUIApp\third_party\win32\protobuf\2.3.0\dll32" /MANIFEST /MANIFESTFILE:"....\Build-vc90\guiapp\Debug\guiapp.exe.intermediate.manifest" /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"e:\GUIApp\Build-vc90\bin\Debug\guiapp.pdb" /SUBSYSTEM:WINDOWS /DYNAMICBASE /NXCOMPAT /MACHINE:X86 libexpatd.lib libpkgPlatform.res odbc32.lib odbccp32.lib comctl32.lib rpcrt4.lib wsock32.lib libprotobuf.lib winmm.lib wxmsw28d.lib wxmsw28d_stc.lib wxexpatd.lib wxPlotd.lib wxpngd.lib wxzlibd.lib wxjpegd.lib wxtiffd.lib xerces-c_2d.lib lua51d.lib libeay32d.lib libexpatd.lib ssleay32d.lib lokisd.lib BugTrap.lib log4cplusD-1_0_4.lib wxcode_msw28d_propgrid.lib wxCode_msw28d_treectrl.lib xrc.lib chartdir50.lib zlibwapi.lib cairo.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib "....\build-vc90\bin\debug\libpkgA.lib" "....\build-vc90\bin\debug\libD.lib" "....\build-vc90\bin\debug\libC.lib" "....\build-vc90\bin\debug\libD.lib" "....\build-vc90\bin\debug\libE.lib" "....\buil "....\Build-vc90\guiapp\Debug\GUIApp.obj" "....\Build-vc90\guiapp\Debug\A.obj" "....\Build-vc90\guiapp\Debug\B.obj" "....\Build-vc90\guiapp\Debug\main.obj" ] Creating command line "link.exe @"e:\GUIApp\Build-vc90\guiapp\Debug\RSP0000131924013376.rsp" /NOLOGO /ERRORREPORT:PROMPT"
能夠看到VC會在連接(link,調用link.exe)以前先推導出該庫所須要的編譯選項,保存到一個臨時文件中,以後以這個臨時文件做爲輸入啓動link.exe進行連接。
須要注意的是,推導依賴庫並建立編譯和連接選項的2個臨時文件,是在編譯(compile)以前作的。這會帶來一個問題,就是若是庫的依賴很是複雜,VC的自動推導過程將會變得很是慢。 2) 開啓任務管理器觀察VC++(devenv.exe)進程的活動狀態
你不可能傻傻的坐在電腦前等着微軟的VC進程恢復到正常狀態,因而你去看會兒書,瀏覽下今天的新聞,又或者去刷刷火車票,在你作完這些以後,回到VC界面,而後發現,我靠,仍是沒有任何LOG輸出。你毅然果真的Kill devexe進程。而後從新打開solution文件,開啓Linker選項中的"Show Progress"選項:Display All Progress Messages (/VERBOSE),又啓動app的build。去看部電影再回來。。。
回來以後,你可能會看到下面相似的輸出:
28>Linking... 28>Starting pass 1 28>Processed /DEFAULTLIB:oleacc 28>Processed /DEFAULTLIB:msvcprtd 28>Processed /DEFAULTLIB:uuid.lib 28>Processed /DEFAULTLIB:libboost_regex-vc90-mt-gd-1_37.lib 28>Processed /DEFAULTLIB:libboost_signals-vc90-mt-gd-1_37.lib 28>Processed /DEFAULTLIB:MSVCRTD 28>Processed /DEFAULTLIB:OLDNAMES 28>AppBase.obj : warning LNK4075: ignoring '/EDITANDCONTINUE' due to '/INCREMENTAL:NO' specification 28>Searching libraries 28> Searching ....\Build-vc90\bin\Debug\xrc.lib: 28> Found "void __cdecl ui_xrc::InitXmlResource(void)" (?InitXmlResource@ui_xrc@@YAXXZ) 28> Referenced in AppCommonObjMgr.obj 28> Loaded xrc.lib(tmp_init.obj) 28> Found "void __cdecl xml_setting_dlg_init(void)" (?xml_setting_dlg_init@@YAXXZ) 28> Referenced in xrc.lib(tmp_init.obj) 28> Loaded xrc.lib(xml_setting_dlg.obj) 28> Found "void __cdecl verify_setup_tflex_submask_editor_init(void)" (?verify_setup_tflex_submask_editor_init@@YAXXZ) 28> Referenced in xrc.lib(tmp_init.obj)
能夠看到VC會自動從推導出的依賴庫中嘗試resolve全部未解決的符號。
3. 方案提出
改爲靜態庫編譯以後,僅僅只是許多obj文件的打包操做,故能夠很是快。 同時改爲靜態庫以後,也避免了相同的符號在最終的app exe運行加載這個DLL後中存在兩份的可能性,由於app exe也能夠連接那些靜態庫。
顯示的告訴VC,一個工程所依賴的庫
對於咱們本身編寫的依賴庫,咱們一般不會顯式的寫到項目的編譯選項中(VC的項目linker頁面input欄),而是會將一些外部依賴的第三方庫寫在那裏,由於那些第三方庫不會頻繁的更改。VC2008中能夠設置庫的相互依賴性(Project/Project Dependencies),而後加上Link Library Dependency=yes。 一種方案是咱們能夠將本身編寫的依賴庫顯式寫到項目的編譯選項中,另外一種方案就是在代碼中寫以下的pragma指示編譯器去依賴某個庫:
#pragma comment(lib, "xxx.lib")
創建一個dummy工程(prebuild)來觸發依賴庫的預編譯
顯式告訴VC編譯器去依賴某個外部庫,而不讓它本身去推導,會帶來一問題就是,當咱們編譯工程時,VC本身不會先去編譯那些顯式依賴的庫(它會認爲它們已經準備好了),它也不知道該怎麼去編譯它們,故若是某個外部庫沒先編譯好,連接工程時就沒法打開那個庫的.lib文件。 有什麼方式可讓VC先去編譯那些外部依賴庫呢?用prebuild命令的方式。
VC提供prebuild命令的機制,容許先運行一個外部命令,而後再編譯工程。固然也支持編譯工程以後再運行一個外部命令,叫作Post-build。 外部命令一般是一個BAT腳本文件,所以能夠寫一個BAT腳原本乾點什麼? 寫一個BAT腳原本一個一個的編譯那些外部依賴庫?代碼以下:
for %%L in (%LIBS%) do ( echo Building %%L ... <<<command to build one lib %%L>>> )
編譯一個庫工程的編譯命令是什麼?直接google,能夠用devenv.com/devenv.exe外部命令帶庫工程的名字辦到(其實devenv.exe是VC的可執行文件),因而寫成下面這樣:?
for %%L in (%LIBS%) do ( echo Building %%L ... devenv.com "%solution%" /Build "%config%" /project %%L )
試想下,若是依賴的庫工程不少,devenv.com外部命令就須要啓動不少次。我想這樣不太好。
想到一句名言:軟件工程中問題,一般能夠引入一個間接層來解決。
若是不斷執行devenv.com命令開銷有點大,能夠引入一箇中間依賴庫libprebuild,讓編譯工程prebuild這個libprebuild依賴庫,更重要的一部是讓libprebuild工程依賴全部原先編譯工程依賴的外部庫。
假如編譯工程是app,之前依賴於庫工程libA, libB, ..... libN,如今引入中間依賴庫以後,讓libprebuild工程依賴於libA, libB, ..., libN,而後讓app先執行prebuild命令,編譯libprebuild工程,這樣一來編譯app工程,會先編譯libprebuild工程,編譯以前VC會自動推導出它的依賴庫,而後就先編譯libA, libB, ..., libN。這樣就好像編譯app工程預先編譯libA, libB, ..., libN同樣。
特別須要注意的是,libprebuild庫工程須要編譯成靜態庫,不然又回到老問題上了。
4. 總結
採用了上述方案以後,任何app工程中若是存在編譯變慢的問題(由於VC龜速的自動的庫推導/展開),編譯採用上述的方案。步驟以下:
在該工程中,增長一個cpp文件,裏面顯示依賴外部庫:
#pragma comment(lib, "A.lib") #pragma comment(lib, "B.lib") ...
寫一個prebuild.bat腳本,裏面寫上相似下面的代碼(我知道Window Batch腳本的語法很怪異):
for %%L in (%LIBS%) do ( echo Building %%L ... devenv.com "%solution%" /Build "%config%" /project %%L )
其中LIBS就是那個建立出來中間依賴庫: set LIBS=libprebuild, config能夠是Debug或者其它。
在工程中的Prebuild Event項的Command欄中寫下以下的指令:
call $(SolutionDir)\libprebuild\prebuild.bat $(ConfigurationName)
最後也是最關鍵的一步: 在Project\Project Dependencies對話框中,取消勾選該工程全部的依賴庫項。
按照上述方法編譯原來的工程,你會獲得< 10 mins的編譯時間。
最後說下該方案存在的問題就是:它不能支持VC界面上的Build Only菜單項,由於無論怎樣,它老是須要去啓動prebuild.bat腳本編譯libprebuild工程。
5. 更好的解決方案 上述方案中引入的中間依賴庫的問題有點繁瑣,其實VC2008已經提供了一個選項"Link => Link Library Dependencies",若是將其改爲NO, 即便在"Project Dependency"對話框中勾選上依賴庫,VC2008將不會進行自動推導並進行依賴庫的展開,可是能夠幫助咱們預編譯那些依賴庫。我想這就是prebuild庫引入想要的解決的問題。這樣一來的話,對於原始問題的解決,咱們的方案就比較讓人滿意了。