原文地址:Using ccache for Fun and Profit 做者 Peter Steinbergerios
咱們的
PSPDFKit
項目超過 60 萬行代碼,而且代碼量還在增加。儘管咱們致力於寫簡潔而高效的代碼,可是這個項目很大,並且有許多邊界狀況須要尤爲注意。在 PSPDFKit 5 for iOS 項目上,編譯時間尤爲成爲一個使人頭痛的問題:每次編譯都很慢。git
咱們的安卓 SDK 也有一樣的問題,幾個月前咱們的安卓負責人在技術棧中引入了 ccache 來處理冗長的 C++ NDK 編譯時間,我也是從那個時候開始接觸 ccache。github
ccache 是一個編譯緩存器,它會在實際編譯以前先檢查緩存。它有直接和預處理模式,並且因爲在 Clang 3.2
版本以前是不支持 ccache
插件,因此在 Clang 3.2
以前會有一些問題,可是如今 Clang
的版本是 3.2.3,因此沒有 Clang
不支持的問題。ccache
是一個具備悠久歷史的項目,其主要焦點是快速正確。shell
網上搜到「ccache xcode」的信息都是過期無效的信息,通過我快速的嘗試網上的方法,都沒法配置好使其正常工做。隨着咱們的代碼庫愈來愈複雜,同時咱們的 Jenkins 工做集羣數也有 10 臺 Mac,如今測試時間從幾乎沒法忍受變成了正真沒法忍受。在 Twitter 抱怨如今天天的工做就是管理 Jenkins 工做集羣以後,Facebook 的 Christian Legnitto(他以前在 Apple 負責 OS X 版本管理工做)建議咱們嘗試 ccache
。編程
使用如下命令安裝 ccache
:xcode
brew install ccache
複製代碼
若是你沒安裝 Homebrew
,請移步這裏,先去安裝 Homebrew
,若是你不想移步,就直接使用如下命令安裝 Homebrew
:緩存
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
複製代碼
爲了讓 Xcode
調用 ccache
,咱們須要一個小腳原本配置一些環境變量,而後再調用 ccache
。將這個腳本保存到您項目的某個地方,並將其命名爲 ccache-clang
。ruby
#!/bin/sh
if type -p ccache >/dev/null 2>&1; then
export CCACHE_MAXSIZE=10G
export CCACHE_CPP2=true
export CCACHE_HARDLINK=true
export
CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches
exec ccache /usr/bin/clang "$@"
else
exec clang "$@"
fi
複製代碼
根據你的具體狀況,若是你的項目中有 C++
的文件,你可能還須要一個命名爲 ccache-clang++
的腳本,並在這個腳本里這麼寫:curl
#!/bin/sh
if type -p ccache >/dev/null 2>&1; then
export CCACHE_MAXSIZE=10G
export CCACHE_CPP2=true
export CCACHE_HARDLINK=true
export
CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches
exec ccache /usr/bin/clang "$@"
else
exec clang++ "$@"
fi
複製代碼
這樣看起來是否是有點複雜,若是沒有命中緩存,那麼將會按照以前的編譯方式同樣編譯,而不是報 ccache not found
(找不到緩存)的錯誤(ccache
內置 shell
腳本,因此檢查緩存很迅速)。工具
建立 shell 腳本方法:
建立 touch ccache-clang
打開腳本 open -a xcode ccache-clang
粘貼腳本內容
執行腳本 chmod 755 ccache-clang
複製代碼
若是去學習 ccache
的配置,你會發現有不少選項可選。上面咱們使用的是一種至關激進的緩存策略,同時運行良好。對於你本身的項目,你可能在沒有 CCACHE_SLOPPINESS
的狀況下開始,而後在一切運行良好的狀況下一次性添加緩存。
這裏最重要的參數是 CCACHE_CPP2
,這個參數用於解決 Clang
將處理預處理器的文件輸出,並可能會發現許多你沒有注意到的潛在問題,例如因爲宏擴展致使的沒必要要的括號。使用此選項會稍微減慢編譯時間,可是要比徹底沒有使用 ccache
要快得多。Peter Eisentraut 寫了一篇關於這個問題的好文章。
您還須要在 Xcode
中定義 CC
變量。在 PSPDFKit
中,咱們在 .xcconfig
文件中執行此操做,這個文件在咱們全部項目中共享(這是一個很好的統一的項目配置,和易於閱讀和查找)。同時,您能夠直接在 Xcode
項目設置內配置:
CC = "$(SRCROOT)/../Resources/ccache-clang"
複製代碼
就這麼多了!下次編譯的時候會比正常慢一點,你能夠在終端中使用 ccache -s
來查看 ccache
是否正常工做。剛開始時應該有不少緩存沒有命中,可是當緩存開始漸漸替代以後的編譯時,編譯速度將會變得快起來。
路不平的地方就有坑:ccache
有一些缺點。
不支持 Clang
的 modules
,若是檢測到 -fmodules
, ccache
就會失效。所以,爲了兼容 ccache
,你須要用老舊的 # import <UIKit/UIKit.h>
替換你項目中全部優雅的 @import UIKit
,以及全部使用 ccache
帶來的問題,比方說宏的問題。在 PSPDFKit
項目中咱們採用了 Objective-C++
的形式,當咱們使用不少 C++
代碼時,就沒法使用 modules
了,因此這一點(ccache
不支持 modules
)並無影響到咱們。 modules
會自動連接用到的 framework
,可是在禁用了 modules
之後,你須要手動添加用到的 framework
,這個工做很無趣,可是也很快就作完。
還須要中止使用 .pch
。蘋果不推薦使用 .pch
,並且通常認爲使用 .pch
是很差的編程風格,哪裏用到就在哪裏導入會比 .pch
要好。對咱們而言,刪除那些 .pch
仍是很容易的。固然,ccache
無法幫你緩存 Swift
文件。雖然 Swift
也使用 Clang
,可是ccache
對 Swift
文件一籌莫展。也許 ccache
最終會支持 Swift
,但我期望不上。由於 Swift
至今沒有穩定,甚至咱們要在 Swift
的兩個版本之間作二進制兼容,咱們無法用 Swift
來編寫咱們的 SDK,因此 ccache
不支持 Swift
的問題,對咱們不是問題。
在編譯期間,咱們應該隨時監視項目是否拋出不兼容的警告。請參閱「不支持的編譯器」選項。我花了至關一部分時間去處理這些不兼容的問題。設置 CCACHE_LOGFILE
臨時環境變量將有助於咱們精肯定位錯誤:ccache
將會提示那些標識是有問題的,以及緩存命中和未命中的具體狀況。
steipete@steipete-rmbp ~ $ ccache -s
cache directory /Users/steipete/.ccache
primary config /Users/steipete/.ccache/ccache.conf
secondary config (readonly) /usr/local/Cellar/ccache/3.2.3/etc/ccache.conf
cache hit (direct) 42530
cache hit (preprocessed) 18147
cache miss 28379
called for link 1344
called for preprocessing 645
compile failed 1
preprocessor error 2
can't use precompiled header 2567
unsupported source language 12
unsupported compiler option 11564
no input file 2
files in cache 124223
cache size 8.7 GB
max cache size 15.0 GB
複製代碼
給你說一下咱們使用的狀況,使用了 ccache
之後,咱們的編譯運行時間平均爲 8 分鐘,以前咱們沒有用 ccache
的時候是 14 分鐘。使用 ccache
以前在最快的 MacBook Pro 上編譯打包整個 PSPDFKit
須要 50 分鐘,使用了以後,時間爲 15 分鐘。添加 ccache
到咱們的技術棧是一個巨大的進步,真後悔我沒有早點知道這個那麼棒的工具!
Anton Bukov 說經過禁用 GCC_PRECOMPILE_PREFIX_HEADER
,開啓 GCC_PREFIX_HEADER
的方式來處理這個問題。