性能比較
1.83 MiB の代表的なコーパス上で lha、
tar、tar.gz、zip を
compress + decompress で比較。各ツールでデフォルトと
最良の両レベルを計測。数字が各ツールの得意分野を
明確に示している。
各セル 5 回、中央値 ± 標本標準偏差。マシン:Apple M シリーズ、 ネイティブ lha ビルド(LHa for UNIX 1.14i-ac20220213、configure デフォルト)。壁時計 end-to-end:各セルの compress + decompress 合計時間。
一覧表
同じ 1.83 MiB コーパスを compress / decompress 各 5 回、 中央値。標準偏差はノート PC 自身のラン間ノイズを反映。
| ツール | レベル | compress | decompress | size B | 圧縮率 | ||
|---|---|---|---|---|---|---|---|
| 中央値 | ±σ | 中央値 | ±σ | ||||
| lha | -lh5- (デフォルト) | ~50 ms | ±5 | ~40 ms | ±1 | 547,045 | 29.93 % |
| lha | -lh7- | ~46 ms | ±1 | ~40 ms | ±6 | 547,045 | 29.93 % |
| tar | 無圧縮 | ~80 ms | ±4 | ~82 ms | ±5 | 2,732,544 | 149.55 % |
| tar.gz | -6 (デフォルト) | ~210 ms | ±5 | ~5 ms | ±0.1 | 455,435 | 24.93 % |
| tar.gz | -9 (最良) | ~210 ms | ±1 | ~5 ms | ±0.1 | 455,435 | 24.93 % |
| gzip -c | -6 (素ストリーム) | ~170 ms | ±4 | ~5 ms | ±0.2 | 455,486 | 24.93 % |
| gzip -c | -9 (最良) | ~170 ms | ±3 | ~5 ms | ±0.2 | 455,486 | 24.93 % |
| zip | -6 (デフォルト) | ~62 ms | ±1 | ~3 ms | ±0.4 | 566,683 | 31.01 % |
| zip | -9 (最良) | ~85 ms | ±1 | ~3 ms | ±0.1 | 566,683 | 31.01 % |
全時間は壁時計 end-to-end。「レベル」は各ツール固有の
圧縮段数。コーパスのファイル数は 304、非圧縮合計 1,827,165 バイト。
tar (無圧縮)の圧縮率が 100 % を超えるのは、
tar が各ファイルのメタデータ(UID、GID、モード、512 バイト
パディング)を保存するため。小さいファイル群では、
deflate をかけない段階で 50 % のオーバーヘッドになる。
表の読み方
1. compress 速度:zip < lha < gzip < tar < tar.gz
デフォルトレベル:
zip -6が最速(~62 ms)— deflate、tar ブロックなし、ファイルあたり少量の オーバーヘッド。lha -lh5-が 2 番目(~50 ms)、 zip より約 22 % 遅い。gzip -6 -c(素 tar ストリーム) ~170 ms — deflate はデフォルトでも LZHUF より はるかに重いエンコーダ。tar -czfは deflate の上に ~40 ms のフレーミング(各レコード、512 バイト整列)。 計 ~210 ms。tar -cf(無圧縮) ~80 ms — ヘッダだけ、deflate なし。
デフォルトでは zipは lhaより 22 %速く、
lhaは tar.gzより 76 %速い。
compress 速度の順序はコーパスを変えても安定している。
2. decompress 速度:tar.gz ≈ zip < lha < tar
decompress では順序が興味深い形で入れ替わる:
zipとtar.gzの decompress は 3-5 ms — deflate デコードは安価。lhaの decompress は ~40 ms — LZHUF のデコードはビット直列:各ビットが 前のビットの表参照に依存する。tarの decompress は ~82 ms — 同じ 512 バイトのパディングコストが出口側にも。
CDN から 100 回展開する実シナリオでは、絶対 decompress 時間は ネットワーク遅延に支配される。3 ツールすべて decompress は 1 桁 ms オーダー;差はディスクシーク + HTTP リクエスト レイテンシのノイズフロアよりはるかに小さい。
3. 圧縮率:tar.gz < gzip ≈ lha < zip
同じ kenlm コーパス:
tar.gz -6とgzip -6 -c共に 455 KB / 24.93 % — deflate のこのコーパスでの 下限。-9はそれ以上の最適化を見つけられない。lha -lh5-~547 KB / 29.93 % — deflate より約 20 % 大きい代わりに、compress が 約 2.2 倍速い。これが 1990 年の LZHUF 設計時点での 意図的なトレードオフ:「286 + 1 MiB RAM での 高速展開」用に調整されていて、サイズ最適化は 二次的。zip -6~567 KB / 31.01 % — zip の deflate 設定が古く、DEFLATE64(ワイドウィンドウ版) がデフォルトでオフ。tar (無圧縮)2.7 MB / 150 % — レコードヘッダ + 512 バイトパディングで小ファイルが 倍以上に膨らむ。
tar.gz と lha の差は 約 5 ポイント —
1.8 MiB 上では約 91 KB。高速 CDN ではこの 91 KB を
再ダウンロードするのに 100 ms 未満。低速 CDN では 1-2 秒。
これが実際の回線上の size-vs-speed トレードオフ。
4. -9 はこのコーパスでは罠
すでに冗長性のあるテキスト(繰り返しのキーワードと空白を 多く含むソースコード)では、gzip -9 と tar.gz -9 は -6 より新しい最適化を見つけられない。コーパスの富度が 足りず、より高いレベルが活きない;-9 は compress 時間で 5-10 倍高いのに圧縮率向上は ≈ 0 %。
真に高エントロピーなコンテンツ(動画断片、データベースダンプ、 暗号化データ)があれば -9 は有意な差を出せる。このコーパスは そうではない。
どのツールをいつ使うか
| 何を気にするか | 選ぶ | 理由 |
|---|---|---|
| 最小出力 | tar.gz -9 / zstd / brotli |
デフォルト 9(または zstd -19 / brotli -11)は単一
バイナリで出せる最良圧縮率。compress 時間が
lha の 5 倍超、圧縮率は 5 % だけ良い。 |
| 最速 compress | zip -6 |
~62 ms。DEFLATE64 オフ、tar ブロックなし、ファイルあたり 少量のオーバーヘッド。deflate -6 より 6 % 圧縮率損失。 |
| 最速 decompress(CDN、OTA、ミラー) | lha または zip |
このコーパスではどちらも 1 桁 ms。lha は出力がやや大きい ものの展開後のサイズがそもそも小さい。ネットワーク遅延が 支配的。「zlib / libiconv 不要」も含めて lha が答え。 |
| CI アーティファクト + CDN の単一静的バイナリ | lha |
x eget use lha で 152 KB の完全静的
バイナリを ~/.local/bin に置く、依存なし。
tar.gzは libz + libiconv + tar が必要、
zipは libzip + libz が必要。lha にはこの
ツリーがない。 |
| Windows / macOS ユーザーが箱から開ける | zip(または .tar.gz) |
zip は Windows + macOS のシステム組込。tar.gz も
ネイティブ。lha はバイナリの事前インストールが必要
(x eget use lha 1 行で済むが、それでも
ステップが 1 つ多い)。 |
| 組込み / ファームウェア / 旧ハードウェア | lha |
制約のあるターゲットで展開コード < 16 KB。640 KB RAM の MS-DOS マシン用に設計。 |
より深い考察
なぜ lha -lh5- は tar.gz -6 より compress が速い
LZHUF(.lh5- の裏方にあるアルゴリズム)は
1990 年設計の静的 Huffman 表 + 高速文字列検索方式。
deflate(gzip / zlib)は LZ77 + 動的 Huffman +
スライディングウィンドウで、エンコーダがウィンドウから
マッチを選ぶ必要がある。ウィンドウを埋めきれない入力
(1.8 MiB は 32 KB ウィンドウに余裕で収まる)では、
deflate はマッチ探索に時間を費やす。
LZHUF はそれをしない。decompress 側は
同じ程度のデコードループ構造で拮抗する。
deflate ウィンドウよりずっと大きなコーパス(-6 で 32 KB)
では、deflate はより長距離マッチを発見できるので
圧縮率で勝る。100 MiB のコーパスなら、
tar.gz -9 は lha -lh5- の
3-5 倍の compress 時間で圧縮率を上回ってくる。
なぜ tar (無圧縮) は入力よりずっと大きい
tar はファイルごとのメタデータ(UID、GID、モード、タイム スタンプ、ファイル名)と 512 バイト/レコードのパディングを 保存する。1.8 MiB / 304 ファイルというコーパスでは、 メタデータが元サイズの 50 % を超える。tar -czf は LZ77 バックエンドでほぼ元に戻すが、生のtar は 小ファイルコーパスでは元より大きくなる。
なぜ zip -6 decompress が tar.gz -6 より速い
zip のアーカイブフォーマットはファイル末尾に
central directory を 1 つ持つ — デコーダは
ストリーミングなしで読める。tar.gz は
tar ヘッダから各エントリのサイズを取り、deflate に
そのサイズを埋めてもらう必要がある。zip の deflate は
central directory をまとめて包める。tar.gz の deflate は
レコード単位 — 各レコードで別ヘッダ解析。差は
ファイル数が多いほど開く。
なぜ lha の decompress は deflate / zstd / lz4 より遅いか
decompress は LZHUF(.lh5-、.lh7- の
裏方)が歳を感じる場面。1990 年設計は ビット直列
(bit-serial):
- 静的 Huffman 表・ビット単位デコード。
ビットストリームを 1 ビットずつ読み、各ビットが次の
表参照を決める。これは
src/huf.cの メインデコードループ — このループには並列性が ない:bit n+1 は bit n の表参照後に しか読めない。 - LZ77 バックリファレンスは直列解決。
デコーダが length/distance ペアを読むと、
distance 前の位置から length バイト
コピーする。
src/slide.cのスライディング ウィンドウはバイト単位で更新、SIMD memmove なし。 1.83 MiB コーパスでは、このコピー部分が実行時間を 支配する。 - ソースに SIMD 一切なし。
grep -nE '(mmx|sse|avx|simd|__m128i|__builtin_ia32)' src/*.cがゼロ一致。vendored 1.14i コードは 1995-2003 年の 純 ANSI C、アーキテクチャ固有ベクトル化なし。
つまりこの問いへの答えは:アルゴリズム自身が上限であり、 実装ではない。LZHUF は 1990 年に「286 + 1 MiB RAM で 高速展開」用に調整されていて、ビット直列デコードループが 自然な解だった。lha に SIMD 最適化を足しても decompress で 1.5-2 倍程度(length-distance コピーだけがベクトル化可能な ブロック)だが、底のビット直列 Huffman ループは根本的に 直列。より速い decompress が欲しければ、アルゴリズムを 変える必要があり、LZHUF のマイクロ最適化では足りない。
比較:zstd の decompress は同圧縮率で lha より
約 5-10 倍速く、lz4 はさらに 3-5 倍速い。
どちらも現代 SIMD CPU(x86_64 上の SSE2/AVX2、
aarch64 上の NEON)向けに調整されていて、decompress ループは
SIMD 友好的に設計されている。lha は MMX(1996)より
6 年早く、最初のメンテナはターゲットにできるベクトル
ユニットを持っていなかった。
これが問題になる場面:lha は「zstd/lz4 が入っていない システムで動く単一静的バイナリ」の正解。lha は マイクロ秒級 decompress が必要な高スループット CDN の正解ではない。
再現方法
任意の Unix 系ホスト(macOS、Linux、BSD)で:
git clone --depth 1 https://github.com/ljh-sh/lha.git
cd lha
bash scripts/build.sh
cp build/src/lha /usr/local/bin/
# コーパス
test -d corpus/kenlm_src || cp -R ../kenlm/upstream/kenlm corpus/kenlm_src
# 各ツール 5 回、中央値。表の数字は Apple M シリーズで 5 回
# 計測した結果(中央値 ± 標本標準偏差)。
for tool in \
'lha :lha c k.lh5- corpus/kenlm_src' \
'tar :tar -cf k.tar -C corpus kenlm_src' \
'tgz6 :tar -czf k6.tgz -C corpus kenlm_src' \
'tgz9 :tar -czf -9 k9.tgz -C corpus kenlm_src' \
'zip6 :zip -qr -6 k6.zip kenlm_src' \
'zip9 :zip -qr -9 k9.zip kenlm_src' ; do
label=${tool%%:*}; cmd=${tool#*:}
rm -f k.*
for r in 1 2 3 4 5; do
rm -rf corpus/kenlm_src; cp -R ../kenlm corpus/kenlm_src
t=$( { time $cmd >/dev/null 2>&1; } 2>&1 | awk '/real/{print $NF}')
sz=$(stat -c%s k.* 2>/dev/null | head -1)
echo "$label t=$t sz=$sz"
done
done
数字はマシンによって違う(CPU 種別で ±10 %、macOS / Linux 横断で
±5 %)が、相対順位
(zip → lha → tar → tar.gz)(compress)、
(tar.gz → zip → lha → tar)(decompress)、
(tar.gz → lha → zip → tar)(圧縮率)は安定。