brk-code

Python のソース 2.0GB を 4.75MB にする — 圧縮ではなく「表現の変換」

901 リポジトリ 2.0GB のソースコードを 4.75MB に収めた手法を、セクション別の実測データとともに解説します。zip では到達できない 421 倍という比率がなぜ可能だったのか。

公開日: 2026-08-09 圧縮静的解析データ構造バイナリフォーマット

901 個の GitHub リポジトリから集めた Python のソースコードは 2.0GB ありました。 これを 4.75MB のファイルに収めています。421 倍です。

一般的な圧縮ツールでは、テキストの圧縮率はせいぜい 5〜10 倍です。 桁が 2 つ違います。種明かしをすると、これは圧縮アルゴリズムの 勝利ではありません。そもそも保存しているものが違います。

前提: コードの大半は捨ててよい

「AI にコードを書かせるための知識」として何が必要かを考えます。

requests.get() を正しく呼ぶために必要なのは、 「get という関数が存在し、urlparamstimeout を受け取り、 Response を返す」という事実です。

get実装は必要ありません。関数の中で何行のコードが動いているか、 どんな変数名が使われているか、コメントに何が書いてあるか—— 呼び出す側には関係ありません。

そこで、次のものを最初から保存しないことにしました。

保存するのは「何が存在し、どう繋がっているか」だけです。 2.0GB のうち、この段階で残るのは数十 MB です。

保存するもの: ファクトグラフ

残った情報を、5 種類の関係に分解します。

defines   モジュールが関数/クラスを定義する
contains  クラスがメソッドを持つ
inherits  クラスが別のクラスを継承する
calls     関数が別の関数を呼ぶ
imports   モジュールが別のモジュールを取り込む

コードは (主語, 述語, 目的語) の三つ組の集合になります。 述語は 5 種類しかないので、3 ビットで表せます。

この時点で「コード」ではなく「知識」になっています。 元のソースには戻せません。一方向の変換です。

実測: セクション別の内訳

実際のファイルは 6 つのセクションからなります。 それぞれの圧縮前後のサイズはこうなりました。

セクション内容圧縮前圧縮後比率
STRTAB文字列表18,160,4872,829,44015.6%
SYMSシンボル表2,545,7901,007,39639.6%
INDEX転置索引1,642,145723,51644.1%
EDGES関係グラフ541,632167,49630.9%
TEMPL鋳型37,61624,29264.6%
METAメタ情報40632880.8%
合計22,928,0764,752,46820.7%

注目すべきは、バイナリ化の段階で既に 2.0GB → 22.9MB になっている点です。 ここが 87 倍。その後の圧縮が 4.8 倍で、掛け合わせて 421 倍になります。

つまり主要因は圧縮アルゴリズムではなく、表現の変換です。

効いている 4 つの機構

1. 文字列インターン

同じ文字列は 1 度しか保存しません。selfstrNone**kwargs は 全体で数万回出現しますが、実体は 1 つで、あとは整数 ID です。

STRTAB が最大セクション(18MB)なのは、修飾名がすべてここに入るためです。 逆に言えば、他のセクションは整数の列でしかありません。

2. varint と差分エンコード

整数を固定長 4 バイトで書くのは無駄です。小さい値は 1 バイトで足ります。 LEB128 形式の可変長整数を使い、さらにソート済みの ID 列は 差分だけを記録します。

生の ID:  [1024, 1025, 1027, 1031]
差分:     [1024, 1, 2, 4]        ← 大半が 1 バイトに収まる

EDGES セクションが 541KB しかないのは、143,251 本の関係を この方式で書いているからです。1 本あたり 3.8 バイトです。

3. テンプレート畳み込み

Python のコードには、同じ形のシグネチャが大量にあります。

def __repr__(self): ...
def __str__(self): ...
def close(self): ...

これらは「引数なし、戻り値注釈なし、デコレータなし」という同じ形です。 形を 1 個だけ保存し、あとは「この形のメンバー」というリストにします。

実測では 126,529 個中 24,005 個(19%)が鋳型に畳み込まれています。 鋳型自体は 37KB しかありません。

4. セクションごとの圧縮方式選択

最後に、各セクションを複数の方式(lzma、zstd、辞書つき zstd、無圧縮)で 実際に圧縮してみて、最も小さかったものを採用します。 推測せず、測ります。

このファイルでは全セクションで lzma が勝ちました。 結果として、読み込み側は標準ライブラリだけで動きます。 外部依存がないぶん、起動も速くなりました。

捨て方の設計 —— ここが一番難しい

サイズを削るだけなら簡単です。全部捨てれば 0 バイトになります。 難しいのは何を残すかです。

当初は重要度スコア(PageRank)の低いものから捨てていました。 これは失敗しました。pandas の DataFrameGroupBy.aggregate のような 主力 API が消え、ビルドスクリプトが生き残るという結果になったのです。

理由は、PageRank が「呼ばれる回数」を測るからです。 公開 API はライブラリ内部からは呼ばれません。呼ぶのは外部の利用者です。

現在は保護層方式を採っています。 「公開されていて、docstring があるもの」は刈り取りの対象から外します。 この変更により、代表 API の保持率が 90.3% から 100% になりました。

詳しくは PageRank で公開 API を選ぶと失敗する に書きました。

結果として得られたもの

指標
収録リポジトリ901
収録 API126,529
元のソース2.00 GB
最終サイズ4.75 MB
圧縮率0.237%(421.6 倍)
検索応答15 ミリ秒
外部依存なし

4.75MB なら、サーバーレス関数のメモリに丸ごと載ります。 検索は 15 ミリ秒で返るので、データベースも要りません。 実際、このサイトはこのファイル 1 個で動いています。

この手法が向かないもの

正直に書いておくと、限界もあります。

バックアップやアーカイブの用途には使えません。

ディスパッチは静的解析では解決できず、記録されません。

元のソースを見る必要があります。

用途を「API の存在とシグネチャを知る」に限定したからこそ、 この比率が成立しています。汎用の圧縮手法ではありません。

まとめ

421 倍という数字は、圧縮アルゴリズムの性能ではなく、 何を保存しないかの設計から来ています。

用途を絞れば、捨てられるものが決まります。捨てられるものが決まれば、 残りをどう表現するかも決まります。汎用の圧縮ツールは用途を知らないので、 この判断ができません。

実際に生成されたデータは収録ライブラリ一覧で 確認できます。仕組みの全体像は技術解説にまとめました。

関連する記事

AI が API の引数を間違える理由 — get() が 678 通り存在するという事実2026-08-08PageRank で公開 API を選ぶと失敗する — 実測で分かった 4 つの誤り2026-08-07

記事一覧へ戻る