
2026/01/11 16:59
**3次元幾何言語におけるユニークな性能最適化**
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Geoscript は、Geotoy ウェブアプリ内で 3‑D ジオメトリを生成・操作するための言語です。
実行はツリー走査型インタープリタ上で行われ、定数畳み込み(constant folding)、計画された共通部分式除去(Common Subexpression Elimination, CSE)、実行時における動的プログラミングメモ化、およびクロスラン持続キャッシュからなる最適化パイプラインへと流れます。
Geoscript プログラムは外部入力を持たないため、ほぼすべての式が 定数 です。
定数畳み込みは
{op:Add,lhs:Literal(1),rhs:Literal(1)} のような算術木を Literal(2) に折りたたみ、さらに icosphere(radius=10,resolution=4) のような閉包やループ全体を事前計算済みのリテラルメッシュに変換します。
CSE は構造ハッシュを用いて同一の AST サブツリーを検出し、それらをメモ化することで重複作業を回避します。
動的プログラミングキャッシュは、実行中に完全に定数な式を記録し、再評価時にはインタープリタを再実行せずにキャッシュされた値を再利用します。
クロスランキャッシュは実行間で永続化されるため、プログラムの変更が小さい場合でも以前の結果を再利用でき、
alpha_wrap(≈900 ms)など高コストな関数の再計算を防ぎます。
乱数生成 (
randv) は特別に扱われます。RNG の初期状態はキャッシュキーに含められ、各呼び出しは (rng_state) の純粋関数として (value,new_rng_state) を返すものとみなされます。
総じて、永続的な式メモ化は Geoscript/Geotoy に最大のパフォーマンス向上をもたらし、開発者にライブコーディング体験を向上させます。
この手法は Nix や Bazel で用いられるビルドシステムキャッシュ戦略と類似しており、コンパイル済みアーティファクトではなく中間 AST ノードに対して実行レベルのメモ化を適用することで、インタラクティブな 3‑D ウェブアプリケーションや同様のグラフィックスツールでの利点を示しています。
本文
過去数か月にわたり、私は Geoscript というプログラミング言語の開発を行ってきました。
この言語は、Web アプリ「Geotoy」内で 3D ジオメトリを生成・操作するために特化しています。
Geoscript の最適化パイプライン
1. 基本的な定数フォールディング
まずは単純な定数折りたたみから始めました。
例えば次のような式
{ op: Add, lhs: Literal(1), rhs: Literal(1) }
を
Literal(2) に置き換え、他の演算やデータ型にも拡張しました。Geoscript のプログラムはほぼ純粋関数で、外部入力や動的な状態(PRNG 呼び出しやメッシュ描画などの副作用を除く)を持たないため、最適化段階で多くのコードが評価されます。
クロージャもキャプチャした変数が定数の場合は最適化され、AST は
render(precomputed_mesh_literal) のように縮約されることがあります。
この手法だけでは実行速度に劇的な向上をもたらすわけではありませんでしたが、同一のメッシュを何度も生成するループには顕著な効果がありました:
spheres = 0..1000 -> || { pos = randv(-1000, 1000) s = icosphere(radius=10, resolution=4) s | translate(pos) } | join
ここでは
icosphere(...) が定数であるため、リテラルメッシュに置き換えられます。その後の平行移動は変換行列だけを更新すればよくなります。
共通部分式消去 (CSE)
次に 共通部分式消去 に取り組みました。
主な手順は以下のとおりです:
- 構造ハッシュ化 – 任意の AST サブツリーを一意の
で決定的にハッシュします。u128 - AST の走査 – ハッシュを計算し、重複を検出して一時変数への参照へ置き換えます(スコープを適切に扱う)。
さらに、プログラム全体での inter‑expression CSE を試み、実行時に完全に定数化された式をメモ化することで、同じ計算が再利用されるようにしました。
実行間での式キャッシュ永続化
ここで画期的な発見がありました。
この定数式キャッシュを インタープリター実行間で永続化 できることです。
Geotoy のようなライブコーディング環境では、開発者はコードの一部だけを頻繁に変更して再実行します。
プログラム全体がほぼ変わらないため、前回の実行で計算された結果を保持し、変更されていない部分についてはキャッシュから再利用できるのです。
メリット:
パラメータ(例:
simplify の許容誤差)が1つだけ変わった場合でも、alpha_wrap のように約 900 ms かかる重い処理はキャッシュから取得され、変更された部分だけが再実行されます。
実例
distance_to_circle = |p: vec3, radius: num| { sqrt(p.y*p.y + pow(sqrt(p.x*p.x + p.z*p.z) - radius, 2)) } radius = 8 0.. -> || randv(-radius*1.1, radius*1.1) | filter(|p| distance_to_circle(p, radius) < 2) | take(1550) | alpha_wrap(alpha=1/100, offset=1/100) | smooth(iterations=2) | simplify(tolerance=0.01) | render
ここでは
alpha_wrap が CGAL ベースの高コスト処理です。キャッシュを永続化することで、
simplify のみが変わった場合にその再評価を省くことができます。
PRNG の取り扱い
Geotoy の組込み PRNG を読み書きする式は、
「
(rng_state) → (T, new_rng_state)」という純粋関数として扱いました。RNG の初期状態をキャッシュキーに含めることで、各実行で再シードされるため同じ呼び出し列が同一の状態遷移を生成し、キャッシュヒットが可能になります。
結論
式メモ化を実行間で永続化することは、Geoscript の最もインパクトのある最適化手法となりました。
これは新しいアイデアではありません(Nix や Bazel などのビルドシステムが使用しているキャッシュと類似しています)が、
Geoscript/Geotoy に特有の「同じプログラムを小さな変更で何度も実行する」というワークフローに最適化されている点が特徴です。