web-dev-qa-db-ja.com

Gitのパックファイルはスナップショットではなくデルタですか?

Gitと他のほとんどのバージョン管理システムとの主な違いの1つは、他のバージョン管理システムがコミットを一連のデルタ(コミットと次のコミットの間のチェンジセット)として保存する傾向があることです。これは、コミットに関して格納することができる最小の情報量であるため、論理的に思えます。ただし、コミット履歴が長くなるほど、リビジョンの範囲を比較するために必要な計算が多くなります。

対照的に、Gitはプロジェクト全体の完全なスナップショットを各リビジョンに保存します。これにより、コミットごとにリポジトリのサイズが劇的に大きくならない理由は、プロジェクトの各ファイルがGitサブディレクトリにファイルとして保存され、その内容のハッシュにちなんで名付けられているためです。したがって、内容が変更されていない場合、ハッシュは変更されておらず、コミットは同じファイルを指すだけです。また、他にも最適化があります。

私がつまずくまでは、これらすべてが理にかなっています パックファイルに関するこの情報 。Gitはスペースを節約するために定期的にデータを入れます。

そのスペースを節約するために、Gitはpackfileを利用します。これは、Gitが2番目のファイルで変更された部分のみを保存する形式で、似ているファイルへのポインターが付いています。

これは基本的にデルタの格納に戻りませんか?そうでない場合、どのように違いますか?これにより、Gitが他のバージョン管理システムと同じ問題にさらされるのをどのように回避できますか?

たとえば、Subversionはデルタを使用し、50バージョンをロールバックすると50の差分を元に戻すことを意味しますが、Gitを使用すると、適切なスナップショットを取得できます。 gitがpackfilesに50の差分も保存しない限り...「少数のデルタの後で、まったく新しいスナップショットを保存する」と言うメカニズムがあり、あまりに大きなチェンジセットを積み上げないようにしていますか? Gitは他にどのようにしてデルタの不利な点を回避できますか?

66
Nathan Long

概要:
Gitのパックファイルは、ディスクキャッシュを効果的に使用し、一般的なコマンドと最近参照されたオブジェクトの読み取りに「適切な」アクセスパターンを提供するように注意深く作成されています.


Gitのパックファイル形式は非常に柔軟です( Documentation/technical/pack-format.txt 、または The Git Community BookThe Packfile を参照)。パックファイルは、2つの主な方法でオブジェクトを格納します。「uneltified」(生のオブジェクトデータを取得し、それをdeflate-compressする)、または「deltified」(他のオブジェクトに対してデルタを作成し、結果のデルタデータをdeflate-compressする)です。パックに格納されたオブジェクトは任意の順序にすることができ(オブジェクトタイプ、オブジェクト名、またはその他の属性で(必ずしも)ソートする必要はありません)、デリートオブジェクトは同じタイプの他の適切なオブジェクトに対して作成できます。

Gitの pack-objects コマンドは、いくつかの heuristics を使用して優れた 参照の局所性を提供します 一般的なコマンド用。これらのヒューリスティックは、洗練されたオブジェクトのベースオブジェクトの選択とオブジェクトの順序の両方を制御します。各メカニズムはほとんど独立していますが、いくつかの目標を共有しています。

Gitはデルタ圧縮されたオブジェクトの長いチェーンを形成しますが、ヒューリスティックは「古い」オブジェクトのみが長いチェーンの最後にあることを確認しようとします。デルタベースキャッシュ(サイズはcore.deltaBaseCacheLimit構成変数によって制御されます)が自動的に使用され、多数のオブジェクト(git log -pなど)を読み取る必要があるコマンドに必要な「再構築」の数を大幅に削減できます。

デルタ圧縮ヒューリスティック

典型的なGitリポジトリは非常に多くのオブジェクトを格納するため、それらを合理的に比較して、最小のデルタ表現を生成するペア(およびチェーン)を見つけることはできません。

デルタベースの選択ヒューリスティックは、同様のファイル名とサイズを持つオブジェクト間で適切なデルタベースが見つかるという考えに基づいています。オブジェクトの各タイプは個別に処理されます(つまり、あるタイプのオブジェクトが別のタイプのオブジェクトのデルタベースとして使用されることはありません)。

デルタベースの選択のために、オブジェクトは(主に)ファイル名でソートされ、次にサイズでソートされます。このソートされたリストへのウィンドウは、潜在的なデルタベースと見なされるオブジェクトの数を制限するために使用されます。 「十分」の場合1 ウィンドウ内のオブジェクト間でオブジェクトのデルタ表現が見つからない場合、オブジェクトはデルタ圧縮されません。

ウィンドウのサイズは、--window=git pack-objectsオプション、またはpack.window構成変数によって制御されます。デルタチェーンの最大深度は、--depth=git pack-objectsオプションまたはpack.depth構成変数によって制御されます。 --aggressivegit gcオプションは、ウィンドウサイズと最大深度の両方を大幅に拡大して、より小さなパックファイルを作成しようとします。

ファイル名の並べ替えは、同じ名前(または少なくとも類似した末尾(.cなど))を持つエントリのオブジェクトをまとめます。サイズの並べ替えは最大から最小の順に行われるため、データを削除するデルタはデータを追加するデルタよりも優先され(削除デルタは表現が短いため)、以前の大きなオブジェクト(通常は新しい)は単純な圧縮で表現される傾向があります。

1 「十分に良い」と見なされるのは、問題のオブジェクトのサイズとその潜在的なデルタベース、および結果のデルタチェーンの深さによって異なります。

オブジェクトの順序付けヒューリスティック

オブジェクトは、「最近参照された」順序でパックファイルに格納されます。最新の履歴を再構築するために必要なオブジェクトは、パックの最初に配置され、それらは互いに接近します。これは通常、OSのディスクキャッシュに適しています。

すべてのコミットオブジェクトは、コミット日付(最新のものが最初)でソートされ、一緒に格納されます。この配置と順序は、履歴グラフを調べて基本的なコミット情報を抽出するために必要なディスクアクセスを最適化します(例:git log)。

Treeオブジェクトとblobオブジェクトは、最初に保存された(最新の)コミットのツリーから保存されます。各ツリーは、深さ優先で処理され、まだ格納されていないオブジェクトを格納します。これにより、最新のコミットを再構築するために必要なすべてのツリーとブロブが1か所にまとめられます。まだ保存されていないが、後でコミットするために必要なツリーとブロブは、ソートされたコミット順で次に格納されます。

最終的なオブジェクトの順序は、デルタベースの選択によって多少影響を受けます。つまり、デルタ表現のためにオブジェクトが選択され、そのベースオブジェクトがまだ格納されていない場合、そのベースオブジェクトは、デリート化されたオブジェクト自体の直前に格納されます。これにより、後で「自然に」パックファイルに格納されていたであろうベースオブジェクトを読み取るために必要な非線形アクセスが原因で発生する可能性のあるディスクキャッシュミスが防止されます。

68
Chris Johnsen

パックファイルでのデルタストレージの使用は、実装の詳細にすぎません。そのレベルでは、Gitはあるリビジョンから次のリビジョンに変更された理由や方法を知りませんが、これらの変更Cを除いて、Blob BがBlob Aに非常に似ていることを知っているだけです。 (そうすることを選択した場合-ブロブAとブロブBを格納することも選択できます)。

パックファイルからオブジェクトを取得するとき、デルタストレージは呼び出し元に公開されません。呼び出し元はまだ完全なblobを見ています。したがって、Gitは、デルタストレージの最適化なしで、常に同じように機能します。

7
Greg Hewgill

gitのシンパックとは で述べたように

Gitはパックファイルでのみデリティフィケーションを行います

gitバイナリdiffアルゴリズム(デルタストレージ)は標準化されていますか? 」で、パックファイルに使用されるデルタエンコーディングについて詳しく説明しました。
いつ、どのようにgitはストレージにデルタを使用するのですか? 」も参照してください。

パックファイルのデフォルトサイズを制御するcore.deltaBaseCacheLimit構成は、Git 2.0.xの場合、まもなく16MBから96MBに引き上げられます。 /2.1(2014年第3四半期)。

David Kastrupによる commit 4874f54 を参照してください(2014年5月):

Core.deltaBaseCacheLimitを96mにバンプ

デフォルトの16mは、大きなファイルと組み合わされた大きなデルタチェーンに対して深刻なスラッシングを引き起こします。

ここにいくつかのベンチマークがあります(git blameのPUバリアント):

time git blame -C src/xdisp.c >/dev/null

sSDドライブにあるgit gc --aggressive(v1.9、ウィンドウサイズは250)で再パックされたEmacsのリポジトリ用。
問題のファイルには、約30000行、1Mbのサイズ、および約2500のコミットの履歴があります。

16m (previous default):
  real  3m33.936s
  user  2m15.396s
  sys   1m17.352s

96m:
  real  2m5.668s
  user  1m50.784s
  sys   0m14.288s
4
VonC