使用 Github 的時候,你有沒有見過下面的提示?
$ git clone https://github.com/torvalds/linux Cloning into 'linux'... remote: Counting objects: 4350078, done. remote: Compressing objects: 100% (4677/4677), done. Receiving objects: 4% (191786/4350078), 78.19 MiB | 8.70 MiB/s
這段提示說,遠端程式碼庫一共有4350078個物件需要克隆。
這就叫"清點物件"(counting objects),Github需要實時計算出來,需要克隆的物件總數。
這個過程非常慢,根據Github的披露,像Linux kernel這樣巨大的庫,清點一次需要8分鐘!也就是說,發出git clone
命令後,會幹等八分鐘,然後才會開始真正的資料傳輸。這當然是無法忍受的。Github團隊一直想解決這個問題。
後來,他們終於發現了一種新的演算法,現在清點一次只要3毫秒!
為了理解這個演算法,你必須先知道,什麼是Git的物件。簡單說,物件就是檔案,最重要的物件有三種。
- 快照物件(Commit)
- 目錄物件(Directory)
- 檔案物件(File)
每次提交程式碼的時候,會生成一個commit物件,裡面有對應的當前"目錄物件"的名字。"目錄物件"儲存了程式碼根目錄所含有的子目錄和檔案資訊。每一個子目錄就是另一個"目錄物件",每一個檔案則是"檔案物件",裡面是具體的檔案內容。
所以,"清點物件"就是清點各種commit、目錄、檔案等。git clone
和git fetch
操作都需要清點物件,因為需要知道,到底下載哪些物件檔案。
清點物件的原始演算法如下。
- 列出本地所有分支最新的一個commit
- 列出遠端所有分支最新的一個commit
- 兩者進行比較,只要有不同,就意味著分支發生變動
- 每一個發生變動的commit,都清點其中具體變動的子目錄和檔案
- 追溯到當前commit的父節點,重複第四步,直至本地與遠端的歷史一致為止
- 加總所有需要變動的物件
上面的過程說明,"清點物件"是一個檔案遍歷演算法,變動的物件會被一一清點到,這就意味著大量的檔案讀操作。對於大型程式碼庫來說,這個過程非常慢。
Github團隊想到的新演算法,是建立一個Bitmap索引,即為每一個commit生成一個二進位制值。
開啟本地Github倉庫的.git/objects/pack/
目錄,你會看到一個索引檔案和一個資料檔案,它們就是Bitmap。簡單說,這兩個檔案索引了當前程式碼庫的所有物件,然後使用一個二進位制值代表這些物件。有多少個物件,這個二進位制值就有多少位。它的第n位,就代表資料檔案裡面的第n個物件。
每個commit都會有一個對應的二進位制值,表示當前快照包含的所有物件。這些物件對應的二進位制位都為1,其他二進位制位都為0。
這樣做的好處是,不用讀取commit物件,只要讀取這個二進位制值,就會知道當前commit包含了哪些節點。更妙的是,兩個二進位制值只要做一次XOR運算,就會知道哪些位(即哪些物件)發生了變動。而且,因為新的物件總是新增到現有二進位制位的後面,所以只要讀取多出來的那些位,就知道當前commit比上一次commit多出了哪些物件。
這樣一來,"清點物件"就變成了二進位制值的比較運算,因此速度極快。進一步的介紹,請參看官方文件《Bitmap的解釋》,《Bitmap的格式》。
目前,Github的生產環境已經部署了這套演算法,使用者再也不用為了清點物件,而苦苦等待了。而且,Github團隊還把它合併進了Git,這意味著,從此所有Git實現都可以使用Bitmap功能了,因此將來肯定還會有更多好玩的用法出現。
(完)