Github 的清點物件演算法

阮一峰發表於2015-09-30

使用 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 clonegit fetch操作都需要清點物件,因為需要知道,到底下載哪些物件檔案。

清點物件的原始演算法如下。

  1. 列出本地所有分支最新的一個commit
  2. 列出遠端所有分支最新的一個commit
  3. 兩者進行比較,只要有不同,就意味著分支發生變動
  4. 每一個發生變動的commit,都清點其中具體變動的子目錄和檔案
  5. 追溯到當前commit的父節點,重複第四步,直至本地與遠端的歷史一致為止
  6. 加總所有需要變動的物件

上面的過程說明,"清點物件"是一個檔案遍歷演算法,變動的物件會被一一清點到,這就意味著大量的檔案讀操作。對於大型程式碼庫來說,這個過程非常慢。

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功能了,因此將來肯定還會有更多好玩的用法出現。

(完)

相關文章