
2026/05/20 20:58
# 従来の 4 つの CPU アーキテクチャにおける LZ4 デコンプレッサー比較 ## 概要 本セクションでは、異なる CPU の処理特性に基づく **LZ4 デコンプレッション性能**の違いについて解説します。 複数のアーキテクチャを比較し、それぞれの強みと課題を明確にしています。 - **対象範囲**: 既存の主流 CPU アーキテクチャ(x86, ARM など) - **目的**: ハードウェアごとのデコード速度特性を把握し、最適な構成を選択する - **結論**: プロセッサの種類によって **性能差が顕著**であることが確認されています ## x86 アーキテクチャの特性 インテル製プロセッサにおける LZ4 デコンプレッサーのパフォーマンス分析結果です。 - **実装背景**: 多数のベンチマークテストを実施済み - **性能評価**: x86 系では高い圧縮効率を維持しつつ、安定した高速処理を実現 - **最適化技術**: CPU 固有命令セットを活用し、**理論的な最大速度**に接近した動作確認 ## ARM アーキテクチャの特性 アーム(ARM)製プロセッサにおける LZ4 デコンプレッサーのパフォーマンス分析結果です。 - **実装背景**: モバイルおよびサーバー向け多様なデバイスで検証済み - **性能評価**: x86 に比べて **絶対速度は劣る傾向**にあるが、消費電力への配慮がなされている - **最適化技術**: ARM 独自の命令セットを適用することで、メモリアクセス効率が向上 ## パフォーマンス比較の要点 異なる CPU 間での LZ4 デコンプレッサーを実際に比較した際の重要な知見です。 - **x86 vs ARM**: x86 は処理速度において **ARM を上回ることが多い** - **ボトルネック要因**: メモリ帯域とキャッシュ階層の違いが、全体の処理速度に大きく影響 - **最適化の重要性**: ハードウェア仕様に応じたコード書き換えにより、**格差を是正可能** ## コード例:CPU 識別によるロジック分岐 実行環境に応じて適切な LZ4 リソースを選択するための実装パターンです。 ```python import platform def select_lz4_decoder(): system = platform.machine() if "x86" in system: return x86_optimized_lz4_decoder() elif "ARM" in system: return arm_optimized_lz4_decoder() else: return generic_lz4_decoder() # 具体的なデコーダー実装はアーキテクチャ固有の最適化コードを使用します def x86_optimized_lz4_decoder(): # x86 向け命令セット(SSE4.2, AVX2 など)を活用した高速化 pass def arm_optimized_lz4_decoder(): # ARM 向け命令セット(NEON など)を活用した高速化 pass ``` ## まとめ LZ4 デコンプレッサーの導入時には、**CPU のアーキテクチャを考慮することが不可欠**です。 - x86: **処理速度優先**なケースに適している - ARM: 消費電力と性能のバランスを重視するケースに適している - 汎用性が必要な場合:環境検知ロジックを実装し、動的に最適化版を選択する
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
以下のバージョンでは、LZ4 のフォーマット詳細に関する不足していた技術的な文脈を取り込みつつ、記事のハイレベルな物語性を維持しています。
概要:
この専用シリーズの最終回において、著者はレトロ 8 ビットプロセッサ向けの LZ4 アルゴリズムの新規分解圧縮実装 4 つを提示します。対象は Z80、Intel 8080/8086 および MOS Technology 6502(Sorcerer 向け)です。これらのバージョンは、SNES や Motorola 68k 向けの以前の作業を拡張しており、現代の開発者がカスタムフレームラッパーなしでレトロハードウェア上で標準的な LZ4 データを実行することを可能にします。
実装は、独自なハードウェアの制限を厳密に最適化しつつ、変長ルーンの処理(長さ 15 以上の場合は初期値 15 に追加バイトを使用)、オフセットワードがゼロの場合を分解圧縮停止シグナルとして扱うなど、具体的な LZ4 フォーマット規則に従っています。著者は、これらの形式には制限事項があることを指摘しており、最後にリテラルのみで nul ルールする必要があること、バックスアップ参照位置に関する特定の制約が存在することを挙げています。
アーキテクチャ的には、各実装は独自の戦略を採用しています。Z80 では効率的なブロック転送命令(
LDIR)を活用し、8080 は同様の論理を等価な命令の欠如により手動ループで実現します。8086 は REP MOVSB などのストリング命令を利用し、「ファ_POINTER」をサポートする一方、6502 はゼロページメモリとカスタムのネステッドループによるブロック転送置換に依存し、カウンタ管理のための特定のバグチェックを含みます。著者はまた、8 ビットチップでは 4 つの単一命令インクリメント(INC BC)を使用してカウンタに 4 を加える最適化を強調しており、このアーキテクチャ上では直接 16 ビット数学を行うよりも高速であると述べています。このリリースはこのシリーズの「最後の壮行」であり、発行後の著者は来週から高レベル言語に戻る予定です。本文
Z80, 8080, 8086, 6502 向けの LZ4 デコンプレッサー実装
SNES プロジェクトのカートリッジ空間節約を目的として、LZ4 圧縮アルゴリズムを採用しました。デコンプレッションにおけるショートカットの活用可能性や、8 ビット/16 ビットプラットフォームとの親和性から着想を得て、プロジェクトを進めるにつれて実装が蓄積されました。 当記事では、Zilog Z80, Intel 8080, Intel 8086, MOS Technology 6502 の 4 アーキテクチャに向けた実装を取り上げます。
LZ4 アルゴリズムと実装の前提条件
ベースとなる動作原理
LZ77 クラスのデコンプレッサーは、以下の 2 つのトリックに基づいています。
- 反復コピー: テキスト内のデータ列が反復する場合、コピーするオフセットと長さ(出力内)を指定します。
- 走査線符号化 (Run-Length Encoding): コピー長が出力の終わりを超える場合、最初に書いた値を反復します。これは複数のバイトコピーでも同様です。
LZ4 のブロック形式は、「文字列(長さ 0 可)」と「後方参照範囲」が交互に並ぶものとして扱います。規格ではこれを「シーケンス」と呼び、基本的な単位は以下のペアです。
- 文字列部分
- その直後の後方参照
データ構造の詳細
各シーケンスは、単一のバイトで開始されます。
- 上位 4 ビット: 文字列長(直接指定)
- 下位 4 ビット: 後方参照長さ(実際には
で解釈)値 + 4
制約と注意点:
- 事実上の最短有効な後方参照長は 4 バイト です(オフセット 0 は禁止)。
- 文字列長が 15 バイトより長い場合、追加のバイト(ニブル値
)が続きます。15 - 同様に、後方参照が 19 バイトより長くなる場合にも追加バイトが続きます。
- 長さが 255 でない値に出会うまで読み込みを続けます。
実装における制限(LZ4 エンコーダー側)
以下の制約はエンコーダー側により保証されています。我々はこれらを前提として動作します。
- 最後のシーケンスは literal(文字列)のみである。
- 最後のシーケンスには少なくとも 5 個の文字が含まれている(ブロックに一意な場合を除く)。
- 直前のシーケンスの後方参照は、ブロックの末尾から少なくとも 12 バイト前を開始する。
これらを組み合わせることで、ゼロバイトのペアで圧縮データを null-terminate し、「後方参照がオフセット 0」を終了シグナルとして利用できます。従来のフレームデータの廃棄が可能になります。
アーキテクチャごとのアプローチの違い
各 CPU は、ポインタ管理やレジスタ構成によって異なります。
| アーキテクチャ | 特徴と戦略 |
|---|---|
| Z80 | LDIR 命令の活用。ブロックコピーが得意。2 つのポインタ(HL, DE)で完結し、スタック利用を最小限に抑えます。 |
| Intel 8080 | Z80 とほぼ同じロジックですが、 が無く手書きループが必要。SBC HL,BC で減算を実行します。 |
| Intel 8086 | 豊富なレジスタ。スタック依存を避け、, , を活用。16 ビット数学をそのまま利用可能。 |
| MOS 6502 | 制限されたレジスタ。ローカル変数はスクラッチスペースに依存。間接インデックスを活用した独自の手法を採用。 |
Z80: 基準とする実装
LZ4 デコンプレッションはブロックコピー(
LDIR)に依存します。Z80 では以下の制限があります。
- ポインタは 2 つしか利用できない(ソースと宛先)。
- サードポインタ値は後方参照時に一時的な「排他使用」として計算され、最終的にスタックに保存・復元される必要があります。
- 長期間続くコピーでポインタをスタックに入れ替える「揺れ」が発生しますが、
命令により HL とスタックトップの交換が可能で、これによりソースポインタの管理が容易になります。XTHL
Intel 8080: Z80 から派生
実装ロジックは Z80 とほぼ同一です。
を手書きループに置き換える必要あり。LDIR- 16 ビット減算を
のような命令で代用(ただし 8 ビット命令の組み合わせ)。SBC HL, BC - アセンブラ記法の違い(
,M
,H/L
など)によりコード ظاهرは異なりますが、本質は同じです。PSW
Intel 8086: レジスタを活用
十分な spare レジスタがあるため、スタック依存を最小化します。
- ファールポインタ(
,DS:SI
)を使い、全 1MB をカバー。ES:DI
,LODSB
,STOSB
などの文字列命令を活用し、Z80 のMOVSB
に相当する動作を実現。LDIR- ストリング処理により、ポインタの同時増分やロード/ストアを 1 命令で処理可能。
MOS Technology 6502: 独自の哲学
レジスタ制限が厳しいため、Z80 や 8080 とは異なるアプローチが必要です。
- メモリからのオペランド: アクミューレータを介さず、全てのデータ入出力が RAM を通ります。
- スクラッチスペース依存: ローカル変数はゼロページや他の RAM に配置し、レジスタ圧力を回避。
- 配列処理の得意技: インデックス演算(
)を多用し、ポインタ管理の困難さを回避します。(src),y
実装の詳細比較(アセンブリコード抜粋)
1. プロローグとメインループ
関数開始時やメインループの構造を比較します。
- 8086 は呼出し規約に従い
を保存/復元する必要があります。BX - 8080/Z80 では HL をソース、DE を宛先として直接使用できます。
| ステップ | Intel 8080 | Zilog Z80 | Intel 8086 |
|---|---|---|---|
| 開始 | (なし) | (なし) | |
| ロードバイト | | | (MOV AL,[DS:SI], INC SI) |
| 保存 | | | (AL を BL に移動して保持) |
文字列処理の違い:
- Z80/8080:
命令でブロックコピーを行う(または 8080 ではループ)。LDIR - 8086:
で同じ目的を達成。rep movsb
; --- 文字列処理の開始点 --- Intel 8080 Zilog Z80 Intel 8086 -------- --------- -------------- main: main: .main: mov a, m ld a, (hl) lodsb ; 1. ロード inx h inc hl push psw push af
後方参照長さの取得処理: 文字列長を読み出し、15 より大きくないかチェックします。
ANI 15 などのマスク操作で高ニブルを分離し、ゼロなら後方参照スキップです。
| ステップ | Intel 8080 | Zilog Z80 | Intel 8086 |
|---|---|---|---|
| 高ニブル抽出 | , , , | x4 | |
| 条件分岐 | | | |
| 長さ取得 | | | |
ブロックコピーの実行:
- Z80:
を使用。ldir - 8086:
を使用。rep movsb - 8080: 手書きループを使用(
,mov a, m
...)。stax d
2. 後方参照処理とポインタ管理
ここが各アーキテクチャで最も異なる部分です。 以下の 4 つのステップをすべて実行する必要があります。
- オフセット計算: 宛先ポインタからオフセットを読み取る(減算)。
- 長さ取得: ソースから残りの長さを読み出し、4 を加算。
- コピー実行: 後方参照ポインタを使用しブロックコピーを実行。
- ポインタ復元: ポインタを元の状態に戻す。
Intel 8086 の手法
スタック操作を最小化するため、
LODSW で 16 ビット値を一度にロードできます。
でゼロチェックが簡単。test ax, ax- オフセット計算後、
,SI
を更新して直接コピー(DI
)を行います。rep movsb
Z80 と Intel 8080 の手法
HL と DE が同時に使用できないため、以下の手順を踏みます。
などで HL とスタックの値を入れ替え、一時的なソースポインタを作成。XTHL- 後方参照ポインタ(HL = DE - BC)を計算。
- ブロックコピー実行。
- ストック操作で元の値を復元。
; --- 後方参照時の Z80/8080 ロジック例 (概念) --- ; HL をスタックに保存 push hl ; ソースポインタの保存 ; HL = DE - BC で後方参照ポインタを計算 ld h, e ; HL の高バイト更新 ld l, a ; HL の低バイト更新 ; ブロックコピー (LDIR) ldir ; 復元 pop hl
MOS 6502 の手法 (間接インデックス活用)
6502 ではスタック操作が非効率なため、ゼロページ上のスクラッチスペース(
.bksrc, .count など)を多用します。
- オフセット計算:
を使用して直接値に書き込みます。SBC - ポインタ管理:
の代わりにLDIR
を使用したループを実装。(src),y - 減算処理: 16 ビット減算がないため、低バイトと高バイトを別々にループする必要があります。
3. ヘルパー関数 .rdlen
(長さリーダー)
.rdlen追加の長さバイト(255 の繰り返しを含む)を読み込んで総長を計算する機能です。
| プラットフォーム | ロジックの特徴 |
|---|---|
| Intel 8086 | でロードし、 で直接足算。条件付き復帰なしなので単純な分岐。 |
| Z80/8080 | アクミューレータで加算 () を使用。スタックへの保存復元が必要になる場合あり。 |
| MOS 6502 | でスクラッチに保存。ループ内で Y レジスタを管理しながら読み込み。 |
; --- MOS 6502 の rdlen 簡易例 --- .rdlen: sta .count ; 初期長を保存 sty .count+1 cmp #$0f ; 15 より長いならループ bne .rdone .rdlp: lda (_src),y ; 次のバイトを読む inc src ; ソースポインタを進める beq + ; 0 または EOF のチェック tax clc lda src adc #$02 ; 長さを加算 sta src ; ... (高バイトの処理)
MOS 6502: LDIR 代替と注意点
6502 は標準的な
LDIR を持たないため、ブロックコピーのための独自のヘルパー関数を実装する必要があります。
これは非常に重要ですが、以下の点に注意してください。
-
16 ビット減算の欠如:
のような単純なループだけでは不十分です。初期値が 256 の偶数倍数でない場合、無限ループまたは誤動作を起こします。DEC COUNTER; BNE LOOP; 正しい減算ループ終了チェック例 ldx .count beq .llp ; X が 0 の場合 .llp: lda (src),y ; コピー sta (dest),y iny bne + ; Y 未完了の場合 inc src+1 inc dest+1 + dex bne .llp ; X を減算して継続 dec .count+1 bne .llp ; 高バイトもチェック -
ポインタの調整: ループ終了時に、コピーされたバイト数の分だけ
とsrc
の高バイトをインクリメントする必要があります(ページ境界などに対応)。destclc tya adc src ; ソース位置の補正 sta src bcc + inc src+1 ; ... (宛先への処理) ldy #$00 ; Y レジスタのリセット rts
結論とプロジェクトへの示唆
API デザインの違い
各アーキテクチャに合わせて柔軟な API を設計しました。
- Z80:
(ソース),HL
(宛先)。終了時、1 バイト過ぎた位置を指す。DE - 8086:
,DS:SI
を使用。呼び出し規約(スタックポインタなど)との親和性が高い。ES:DI - 6502: 引数はゼロページ上の固定アドレス(
,.src
)。レジスタ依存を避ける設計。.dest
開発者へのアドバイス
このプロジェクトを通じて得られた最大の知見は、**「80 ビット CPU(Z80/8080)と 6502 は、一見似ていてもレジスタ管理の観点では全く異なるアプローチが必要」**ということです。
- Z80 で培った「LDIR への依存」は 8080/8086 ではそのまま適用できますが、6502 では完全に再考する必要があります。
- 特に Z80 と 6502 を両方扱うと、初期の開発コストが跳ね上がりますが、長期的にはそれぞれの強み(Z80 の高速なポインタ操作 vs 6502 のメモリベース計算)を最大限に活用できます。
まとめ
4 アーキテクチャすべてで動作する LZ4 デコンプレッサーの実装は達成できました。コードは互換性がありつつも、ハードウェアの制約に応じて最適化されています。特に Z80 と 6502 の経験値が同等になり、プロジェクト間で適切に切り替える(ギアチェンジする)ことが可能になりました。
次週からはより自由なテーマへ向けて活動します!