
2026/07/14 3:21
Lua ストリングリテラルの最適化で 400 バイト節減
RSS: https://news.ycombinator.com/rss
要約▶
日本語翻訳:
2026 年 7 月 2 日に投稿されたゲスト記事において、著者 Yuki は ComputerCraft/Tweaked の共有ペットプロジェクトにおける重大なボトルネックについて触れています。既存の Lua コードベース(ドキュメントを含む)は 440 KB を超え、ディスクの 125 KB の制限を超えているが、コンピューターやタートルの 1 MB の制限には満たしています。初期の戦略として CR(改行文字)を未使用のバイトで置換することは有望でしたが、同時に CR とエスケープ文字を置換しようとすると一様分布においてファイルサイズが約 1% 増加し、単純な置換は非効率であることが示されました。これを克服するために、Yuki は特定の文字列(エスケープ文字/CR)を 16 ビットから 9 ビットへと圧縮する高度な符号化スキームを提案しています。特に重要なのは、「実際に CR か」のフラグをビット論理(
bit32.btest、bit32.band)を用いてバンド外で保存することで空間効率を最大化することです。Rust のトリック——core::iter::repeat(0..7).flatten()——はこれらのビットインデックスを高速に計算するために使用され、モジュロ演算や列挙のオーバーヘッドを排除することで、仮想コンピューターの性能が高い状態を保証しています。このブリークスルーにより、Cobalt ランタイムにおける従来の圧縮失敗が解決されました(同ランタイムは生文字列、改行文字、エスケープ文字に関する独自な文字処理ルールを持つことに留意のこと)。これによって、複雑なシステムセットアップも極めて制限されたハードウェア上で実行可能となりました。その結果として、wget などの標準ツールの単一の URL ダウンロードを通じて即座にインストール可能な自己解凍アーカイブが実現しました。本文
Lua コード圧縮技術と Raw String による CR エスケープの実装
背景と課題
- プラットフォーム: CC: Tweaked (ComputerCraft/Turtle)
- スクリプト言語: Lua
- 容量制限:
- 通常設定(1 MB)
- ディスクエチケット形式(125 KB)
- 現状の問題:
- 開発チームのプロジェクト「ComputerCraft pet」は、これらの制限を大きく上回るデータ量(約 440 KB)を使用。
- フロッピーディスク 1 枚に収まらず、単一のファイルとして配布・インストールする必要がある。
圧縮戦略と目標
- 目的: Lua コードのテキストデータを圧縮し、容量制限内に収める。
- 要件:
- 自己展開型アーカイブとしての動作。
- 最小限の解凍速度(出力サイズと性能のバランス)。
- 手法:
のようなアルゴリズムではなく、Lua 内蔵機能を活用した独自のシリアライゼーションを実装。bzip2
コード圧縮のための文字列処理技術
テーブルキー表記形式
テーブル内のキー表現には 2 つの方式があるが、実用上は大きな差分がないと判断。
-
識別子ベース
{ key = 10 }- 有効な Lua 識別子のみ使用可能。
- 変換後の扱い:文字列として扱われる。
-
表現式ベース
{ ["key"] = 10 }- 全てのカテゴリーに対応する安全な表現。
結論: どちらを使用しても大きなメリットの差は見られず、現状では両者とも実用的。
Raw String(生文字列)の処理課題
Lua(特に Cobalt ランタイム)における文字列はバイト文字列である。記述方法は以下の通り。
- シングルクォート
'meow'- バックスラッシュによるエスケープ有効。
- ダブルクォート
"meow"- バックスラッシュによるエスケープ有効。
- Raw String
または[[meow]][=[...]=]- バックスラッシュを無視。改行も特別な意味を持たない。
Raw String の構文ルール
- 開始・終了記号:
の後の=[
の数を指定し、その数と同じ数の=
で終える(例:=
)。[=[ ... ]=] - 制約: 開始部と終了部に他の文字を含めることはできない。
- 有効/無効の例:
- 無効:
[=[meow [=[ mrrp]=] - 有効:
[[meow [=[ mrrp]] ]=]
- 無効:
Raw String 特有の動作特性
1. テーブルキー内の Raw String
- スペースを含む必要がある。
{ [ [[meow]]] = 10 }
2. 改行文字(Newline)の扱い
- Cobalt は複数の改行(CRLF, LF, CR など)を常に単一の LF に折りたたむ。
- ファイル形式(DOS/Unix)の違いに影響されないため、
のような事前処理不要。dos2unix
3. 先頭の改行の無視
- Raw String が改行で始まる場合、その最初の改行は無視される。
-- 以下の式はどちらも True print("meow" == [=[ meow ]=]) print("\nmeow" == [=[ meow ]=])
CR(キャリッジリターン)の解決策
Raw String はバックスラッシュをエスケープに使えないため、改行(CR)や特殊文字をそのまま扱えない問題がある。これを解決するために
gsub を利用する手法を検討した。
アプローチ 1: 単純な置換と再復元
- CR のみ別の文字に置き換え、後で復元する。
- 欠点: 文字列のバイト分布がほぼ一様の場合、文字列サイズが約 1% 増加する。
アプローチ 2: オフバンド(Out-of-band)データを用いた効率化
- 「実際に CR か?」という情報をビットストリングで外部に格納し、
のコールバック関数で復元する手法。gsub
実装コード
([[string]]):gsub('E', (function(i) return function() i = i + 1 return bit32.btest( ('V'):byte(math.floor(i/7)), 2^(i%7) ) and '\r' end end)(6))
コードの解説
- 変数
: CR のエスケープ文字。E - 変数
: バイト値を持つビットストリング。V - 最適化ポイント:
- ローカル変数の活用:
で初期化(コールサイトより位置で 6 に固定)。function(i) - ビット操作の効率化: 下位 7 ビットのみ利用し、最高位をセットすることで特殊文字との衝突を回避。
- 安全な返却値: コールバックが
/nil
を返すと置換されないので、false
で安全に CR に変換。and '\r'
- ローカル変数の活用:
効果
- エンコード効率: CR のオーバーヘッドは約半分になる。
- ビット数削減: エンコードサイズを 16 ビットから 9 ビットへ圧縮可能。
バonus: Rust パースペクティブからの考察
Rust におけるイテレータの書き方を参考に、清潔感のあるコード構成を検討した。
代替案:ビットインデックス生成
core::iter::repeat(0..7).flatten()
- 機能:
をループさせるイテレーターを生成。0, 1, ..., 7 - 利点:
や% 7
よりシンプルで視認性が高い。.enumerate()
まとめ
- 容量制限問題: Lua コードのテキストデータを、自己展開型アーカイブとして扱う必要がある。
- Raw String 活用: バックスラッシュを使えない Raw String の特性(改行扱い、エスケープ不能)を逆手に取り、ビット演算と
を組み合わせた独自の圧縮ロジックを実装。gsub - 技術的功成り:
- CR のオーバーヘッドを大幅に削減(約半分)。
- エンコード効率を向上(16 bit → 9 bit)。
- Rust 的なイテレータ思考を取り入れたコードの可読性を維持。