Git:圧縮とリポジトリのサイズ

2012-07-17 15:18 JST by kcrt

 

Git便利ですね。Gitではオブジェクト(ファイルなど)をzlibで圧縮して保持します。それだけではなく、差分を計算したり、複数のオブジェクトを packすることで、単純にすべての履歴を記録するよりも効率的にリポジトリを保持することができます。

% git --version
git version 1.7.9.2
% alias | grep dirsizeinbyte
dirsizeinbyte='find . -type f -print -exec wc -c {} \; | awk '\''{ sum += $1; }; END { print sum }'\'

duだとファイルサイズではなくてブロックサイズになるので上記コマンドを使います。Landscape - エンジニアのメモを参考にしました。

まずはgit initしただけの状態で。

% git init
Initialized empty Git repository in /Users/kcrt/repostest/.git/
% cd .git
% dirsizeinbyte
13917

13.6KiBです。ここからどうなっていくかを見て行きましょう。まずはテキストファイルから。

% cd ..
% perl -e 'for(1..1000000){print "This is a large text file\n";}' > textfile
% wc -c textfile
26000000 textfile
% git add textfile
% git commit -m "textfile"
[master (root-commit) f274f23] textfile
1 file changed, 1000000 insertions(+)
create mode 100644 textfile
% cd .git
% dirsizeinbyte
165888

Gitはファイルをテキストファイルとして認識してくれました。巨大なテキストファイル(24.8MiB)を追加したにもかかわらず、リポジトリのサイズは162KiBに収まっています。これは繰り返し文字列でありzlibによる圧縮が良好に効いていることによります。

ファイルの一部(10000行目のみ)改変してみましょう。

% cd .. 
% vi textfile
(10000行目のみ改変)
% cat textfile | grep test 
10000:This is a large test file
% git add textfile 
% git commit -m "text->test" 
[master a87fd52] text->test
 1 file changed, 1 insertion(+), 1 deletion(-)
% cd .git 
% dirsizeinbyte 
317734

Gitは一行のみの変化であることをきちんと認識してくれました。しかし、リポジトリのサイズは310KiBとなっています。先ほどのリポジトリのサイズが162KiBなのでほとんど倍です。圧縮されて効率的なのは確かですが、ちょっと期待していた動作と違います。1行だけの変更なので、もう少し小さなサイズになることを期待していました。

Gitは、コンテンツの変更の際も単純にリポジトリに新しいオブジェクトとして追加します。これは高速に動作しますが、ディスク容量の観点からは非効率です。差分を探して、不必要なオブジェクトを削除するにはgit gcコマンドを使います。

% git gc 
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), done.
Total 6 (delta 1), reused 0 (delta 0)
% cd .git 
% dirsizeinbyte 
79679

リポジトリのサイズは78KiBと十分に小さくなりました。一つ目のファイルを追加した際のサイズより小さくなっています。

git gcコマンドは上記のように手動で実行することもできますが、必要と考えられるときやリモートリポジトリへのpushを行う時には自動で実行されます。

それではバイナリファイルの実験に入りましょう。リポジトリを初期化して開始します。

% yes | \rm -R .git 
% git init 
Initialized empty Git repository in /Users/kcrt/repostest/.git/
% perl -e 'for(1..1000000){print "\x00\x01\x02\x03\x04\x05\x06\xfd\xfe\xff";}' > binfile 
% wc -c binfile 
 10000000 binfile
% git add binfile 
% git commit -m "binfile" 
[master (root-commit) 8580c04] binfile
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 binfile
% cd .git 
% dirsizeinbyte 
67942

Gitはファイルをバイナリ(binfile)として認識してくれました。binfileは9.5MiBと大きなファイルですが、繰り返しが多いのでzlibによる圧縮が効率的に行えそうです。リポジトリのファイルは66KiBと期待通り小さなものになりました。

一部を改変するとどうなるでしょうか。

% ..
% vi -b binfile
(:%! xxd 編集 :%! xxd -r)
% git add binfile                                                                   
% git commit -m "small change"                                                      
[master c05696a] small change
 1 file changed, 0 insertions(+), 0 deletions(-)
% cd .git                                                                           
% dirsizeinbyte                                                                     
121847
% git gc                                                                            
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), done.
Total 6 (delta 1), reused 0 (delta 0)
% dirsizeinbyte                                                                     
36149

binfileに16バイトのわずかな変更を加えました。git gcを行うとリポジトリのサイズは35KiBとなりました。バイナリファイルでも差分による圧縮とzlib圧縮を行なって効率的にリポジトリにファイルが格納されることがわかりました。