演算法 — 歷史、效能、當前定位

lha 攜帶的是 LZHUF: 1990 年代的 LZ77 + 動態 Huffman,為「1 MiB 記憶體的 286 上的快速解壓縮」而設計,寫在 SIMD 還沒出現的年代。本 頁說的是這個演算法、它在 2026 年付出的代價、以及為什 麼一份外部專案的單一靜態二進位檔,仍然會以它為 底座。

演算法 · 歷史 · 效能 · 當前 · 總結

1. 演算法

lha 家族裡每一種壓縮方式(-lh0--lh7-,外加 -lhd- 目錄標頭)都 是同一個兩段式結構:

  1. LZ77 風格的字串比對。在輸入裡找重複的位元 組序列,並發出形如 (distance, length) 的回引用,代替重複位元組。所有 LZARI / LZHUF / ZIP / gzip / zstd / lz4 / 7-zip 裡的「LZ」都是同一 個想法,源頭是 Lempel & Ziv 1977 年的論文。
  2. 熵編碼器,作用在字面值與回引用上。這是各家 不同的部分:
    • -lh1-LZARI: LZ77 + 算術編碼(Okumura,1988)。
    • -lh5-LZHUF: LZ77 + 動態 Huffman(Tagawa,1990)。預設方式。
    • -lh4--lh6--lh7--lzs- — 熵後端的實驗性改良,或為相容性而保留的未使 用編碼。

靜態 vs 動態 Huffman

LZHUF 用的是靜態 Huffman 表,每個區塊重算一次, 輸入裡每幾 KB 重新推導一次。這個設計抉擇決定了整組取捨:

位元串列解碼迴圈(以及為什麼它慢)

LZHUF 的解碼迴圈逐位元讀入位元流,每次透過一 個小型 2× 表查找下一個符號。這在本質上就是串列的: 位元 n+1 必須在位元 n 查表完成後才能讀。

ANSI C 源碼就是直白的 1995-2003 年程式碼,沒有 SIMD intrinsic:

$ grep -nEi '(mmx|sse|avx|simd|__m128i|__builtin_ia32)' upstream/lha/src/*.c
$                       # 零命中

把 LZ77 的 length-distance 複製向量化,可以拿到大約 1.5–2× 的解壓縮速度;位元串列的 Huffman 迴圈就是 天花板。要在解壓縮上更快,必須換演算法,不能再微觀最佳 化 LZHUF(更長的版本見 基準頁)。

2. 歷史 — 世系

首頁的歷史章節對 1988-2022 這段弧線講得很詳細;緊湊版:

3. 效能 — 三條軸

基準資料來自 1.83 MiB 的 kenlm 原始 碼語料,每個 cell 跑 5 次,取中位數 ± 樣本標準差。要點:

工具等級壓縮 解壓縮壓縮率
lha-lh5-(預設) ~50 ms~40 ms 29.93 %
zip-6(預設) ~62 ms~3 ms 31.01 %
tar(不壓縮) ~80 ms~82 ms 149.55 %
tar.gz-6(預設) ~210 ms~5 ms 24.93 %

三條值得記的觀察

  1. 壓縮速度。lha 排在 zip(最快,領先 ~22 %)和 tar.gz(最慢,慢 ~4×)之間。對於「壓縮 後留存」類型負載(建置產物留存、鏡像站),lha 是非平凡壓縮器裡最便宜的,仍然比裸流傳得快 ~3×。
  2. 解壓縮速度。ziptar.gz 解壓縮在個位數毫秒;lha 要 ~40 ms。位元串列 Huffman 迴圈就是天花板。絕對時 間仍然很小 — 遠低於任何 CDN 的網路 RTT 雜訊。 在 CDN 上從鏡像重新解壓縮 100 次的工作負載,被 HTTP 延遲支配,而不是演算法選擇。
  3. 壓縮率。lhadeflate 高 ~5 個百分點(1.8 MiB 源碼上多 ~91 KB)。這是 1990 年的刻意取捨:調校目標是 286 上解壓縮快,而不是 2020 年工作站上壓得最緊。

哪裡輸給現代演算法 — 哪裡不輸

基準頁的「深入」章節講演 算法內部原因。短版本:lha 是 1990 年的設計,到今天沒變過, 速度天花板就是演算法本身,不是實作。

4. 當前定位 — 為什麼這個專案在 2026 年還存在

被 vendored 的 jca02266/lha@86094cb 源碼, 是 1995-2003 年代的 ANSI C 程式碼庫,2022 年只經歷過一次 現代化(autotools)。ljh-sh/lha 從那裡出發只做一件事: 為每個平台產出一個 lha 的單一靜態二進位 檔,讓使用者不需要編譯器

2026 年這個命題有三類具體需求情境:

4.1 老 .lzh 的解壓縮

最直觀的情況:某人手上有 1994 年的遊戲補丁、2ch 風格 的 BBS 歷史附件、或 2000 年代的日本開放原始碼封存檔。 現代工具都打不開 .lzh。裝上 x eget use lha,兩秒鐘就到 ~/.local/bin,再 lha x foo.lzh 就能解。少了這個二進位檔, 使用者只能裝編譯器、找 1990 年代的 autoconf 補丁、然 後祈禱他們的 libc 還沒把這個建置搞壞。

4.2 單檔靜態二進位檔用於 CI 產物 / CDN 分發

lha 從任何源碼樹產出一個 .lzh 封存檔,耗時 ~50–80 ms,主機上零系統相依性。對 於 CI 產物發布者(任何把建置結果打包下載的人來說),意 思是:

這就是 ljh-sh 在各專案裡都採用的「靜態二進位檔分發慣例」 (kenlm、upxz、現在的 lha)。具體管線可看 建置稽核

4.3 x-cmd 的 zuz 模組 — 直接動機

這是本專案以靜態建置形式分發的根本理由。

x-cmdzuz 模組對封存檔做格式自動辨識:按副檔名選工 具,透明地解壓縮或壓縮。它支援 tartar.gztar.bz2tar.xzzip7zrarlha 等。但它有一條架構鐵律:

絕不內嵌格式專用的工具。永遠呼叫系統已安裝的 二進位檔。

問題落在 x-cmd issue #131(2024 年 12 月):某個使用者希望 zuz 支援 LHA/LZH,但 x-cmd 當前支援的所有 可攜式套件管理程式都不內建 lha。討論線索 如下:

也就是說,zuz 使用者的主要安裝路徑是:

x uz foo.lha
# zuz 偵測到 .lha,發現 PATH 裡沒有 lha,
# 回退到:eget ljh-sh/lha --to ~/.local/bin/lha
# 然後:~/.local/bin/lha x foo.lha

不是 Homebrew。不是 apt。不是各發行版的套件。一個預 設的靜態安裝,不需要特權安裝、不需要 tap、不需要建 置鏈、在 x-cmd 支援的每個平台上都能用。本倉儲存在的全部 理由,就是為它提供這一份「預設靜態安裝能力」。

如果你是 x-cmd 使用者,在一台沒裝 lha 的系統 上 x uz foo.lha,你就是在用這個二進位檔。 如果你從來沒用過 x-cmd,只是要打開一個 .lzh,你也在用這個二進位檔 — 同一份 產物、同一個 release、同一個被 vendored 的源碼。

4.4 這個專案「不是」什麼

老實講清楚邊界:

5. 總結

想要一條經驗法則:

如果你在意…就選
打開一個 1994 年的 .lzh lha(本專案的二進位檔)
封閉系統上的單一靜態二進位檔 lha(~150 KB,零相依性)
類似 zuz 的格式自動辨識 lha(惰性從 ljh-sh/lha 拉)
長尾 CDN 上最小的產物 tar.gz -9zstd
微秒級解壓縮 lz4zstd

本頁存在的理由是:「這個二進位檔是什麼」和「為什麼它 存在」值得集中在一個地方,而不是被 歷史基準建 置稽核FAQ 各頁瓜分。