效能比較

在 1.83 MiB 的代表性語料上對 lhatartar.gzzip 做 壓縮與解壓縮比較,每個工具都跑了預設與最佳兩級。 數字清楚地說明了每個工具在哪一項領先。

每格 5 次取中位數,附樣本標準差。機器:Apple M 系列, 原生 lha 編譯產物(LHa for UNIX 1.14i-ac20220213,預設 configure 選項)。Wall-clock 端到端:每格的 compress + decompress 總時間。

← 資安稽核 · ↑ 站台

一眼看完的表

同一 1.83 MiB 語料,壓縮解壓各 5 次,取中位數。標準差反映 筆記型電腦本身逐次運行的雜訊。

工具 級別 compress decompress size B 壓縮率
中位±σ中位±σ
lha-lh5- (預設) ~50 ms±5 ~40 ms±1 547,04529.93 %
lha-lh7- ~46 ms±1 ~40 ms±6 547,04529.93 %
tar無壓縮 ~80 ms±4 ~82 ms±5 2,732,544149.55 %
tar.gz-6 (預設) ~210 ms±5 ~5 ms±0.1 455,43524.93 %
tar.gz-9 (最佳) ~210 ms±1 ~5 ms±0.1 455,43524.93 %
gzip -c-6 (原始流) ~170 ms±4 ~5 ms±0.2 455,48624.93 %
gzip -c-9 (最佳) ~170 ms±3 ~5 ms±0.2 455,48624.93 %
zip-6 (預設) ~62 ms±1 ~3 ms±0.4 566,68331.01 %
zip-9 (最佳) ~85 ms±1 ~3 ms±0.1 566,68331.01 %

所有時間均為 wall-clock 端到端。「級別」是各工具自帶的 壓縮段位。語料檔案數 304,未壓縮總位元組 1,827,165。 tar (無壓縮)的壓縮率超過 100% 是因為 tar 儲存每個檔案的 metadata(UID、GID、mode 位元、512 位元組 每記錄 padding);對零碎小檔案來說,沒經過 deflate 之前 就已經多了 50%。

怎麼讀這張表

1. compress 速度:zip < lha < gzip < tar < tar.gz

在預設級別下:

在預設級別上,ziplha快 22%, lhatar.gz快 76%。 compress 速度的相對排序在不同語料上穩定。

2. decompress 速度:tar.gzzip < lha < tar

decompress 方面速度排序很有趣:

在 CDN 上解壓 100 次的真實情境裡,絕對 decompress 時間被 網路延遲主導。三種工具的 decompress 時間都在個位數毫秒級; 差異遠低於磁碟 seek + HTTP 請求的雜訊底限。

3. 壓縮率:tar.gz < gziplha < zip

同一個 kenlm 語料上:

tar.gzlha 的差距約 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%。

如果你有真正高熵的內容(影片片段、資料庫 dump、加密資料), -9 會拉開有意義的差距。本語料不滿足這個條件。

什麼場景用什麼工具

如果你關心的是…原因
最小可能的輸出 tar.gz -9 / zstd / brotli 預設 level 9(或 zstd -19 / brotli -11)是從單檔案 工具中能得到的最優壓縮率。代價是 compress 時間 比 lha 慢 5 倍多,壓縮率只差 5%。
最快的 compress zip -6 約 62 ms。DEFLATE64 關閉,沒有 tar blocking, 每檔案少量開銷。比 deflate -6 損失 6% 壓縮率。
最快的 decompress(CDN、OTA、鏡像) lhazip 這兩種工具在本語料上都在個位數毫秒級。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 一行解決,但仍然是一個步驟)。
嵌入式 / 韌體 / 舊硬體 lha 在受限目標上解壓器 < 16 KB。設計目標是 640 KB RAM 的 MS-DOS 機器。

深入閱讀

為什麼 lha -lh5- compress 比 tar.gz -6

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 (無壓縮) 比 input 大那麼多

tar 記錄每個檔案的 metadata(UID、GID、mode、時間戳、檔名) 加上 512 位元組/記錄 padding。我們 1.8 MiB / 304 個小檔案 的語料裡,metadata 占 50% 多。tar -czf 透過 LZ77 後端能 把大部分壓回去,但原始tar 對小檔案語料就是 比 input 大。

為什麼 zip -6 decompress 比 tar.gz -6

zip 的存檔格式在檔案結尾有一個 central directory — 解碼器可以非串流式地讀它。tar.gz 必須對 每筆記錄掃 tar header 來找 size,然後讓 deflate 解碼器填 那個 exact size。zip 的 deflate 可以 wrap 整個 central directory; tar.gz 的 deflate 是 per-record 的,每筆記錄要單獨解析 header。差距在檔案數變多時拉大。

為什麼 lha 的 decompress 比 deflate / zstd / lz4 慢?

decompress 是 LZHUF(.lh5-.lh7- 背後 的演算法)顯出年代感的場合。1990 年設計是位元串列 (bit-serial)的:

也就是這個問題的答案是:演算法本身就是上限,而非實作。 LZHUF 在 1990 年針對「286 + 1 MiB RAM 上快速解壓」做 調校,位元串列解碼迴圈就是天然的解。給 lha 加 SIMD 優化 估計在 decompress 上能拿到 1.5-2 倍(length-distance 複製 是唯一能向量化的區塊),但底層的位元串列 Huffman 迴圈是 根本地序列。為更快的解壓,必須更換演算法, LZHUF 的顯微最佳化不夠。

比較:zstd decompress 比 lha 在同等壓縮率下 快約 5-10 倍,lz4 還能再快 3-5 倍。兩者 都以現代 SIMD CPU(x86_64 上的 SSE2/AVX2、 aarch64 上的 NEON)為標的調校,decompress 迴圈是 SIMD 友善地設計。lha 比 MMX(1996)早六年,最初的 維護者沒有可打的向量單元。

這在何時重要:lha 是「zstd/lz4 沒裝的系統上能跑的單檔案 靜態二進位」的正解。lha 不是需要微秒級 decompress 的高吞吐 CDN 的正解。

怎麼重現

在任何 Unix-like 主機上(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 次取中位數。表中的數字來自 5 次(median ± sample-stdev),
# Apple M 系列筆電。
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%),但 compress 速度的相對排序 (zip → lha → tar → tar.gz)、decompress (tar.gz → zip → lha → tar)、壓縮率 (tar.gz → lha → zip → tar) 是穩定的。