より多くの言語が学ぶべきOxCamlの無割り当て関数チェック
(theconsensus.dev)- Jane StreetのOCamlスーパーセットであるOxCamlは、
[@zero_alloc]により、関数呼び出しツリー全体でヒープ割り当てを禁止することをコンパイラに宣言できる - 宣言された呼び出し経路で割り当てが発生するとコンパイル失敗として即座に明らかになり、後からプロファイラで見つける方式よりも回帰をすばやく防げる
- C、C++、Java、Go、C#、Rust、Zig、OCamlなどでは通常、ホットパスの割り当てをプロファイラで見つけて減らすアプローチが中心となる
- ホットパスのコードは小さな修正だけでも再び割り当てを生み出す可能性があり、既存の最適化の文脈を忘れると同じ調査を繰り返すことになる
- Zigや最近のRustの一部にあるallocatorを渡す慣例も役に立つが、慣例よりもコンパイラによるチェックのほうがより直接的な安全装置になる
[@zero_alloc]が変える割り当て管理
- OxCamlはJane StreetのOCamlスーパーセットであり、関数がヒープに割り当てを行わないというアサーションをサポートする
- 関数に
[@zero_alloc]を付けると、その関数だけでなく、その下の呼び出しツリーまでヒープ割り当ての有無をコンパイラが確認する - 呼び出しツリー内で割り当てが発生するとビルドが失敗し、コンパイラが割り当ての発生を知らせる
- 静的解析で似たチェックを作れる可能性はあるが、生成された要約を見る限り、このような機能をコンパイラ自体に組み込んだ主流言語はまれである
プロファイラ中心のアプローチとの違い
- 他の言語では通常、プロファイラで割り当てを見つけた後、特に何百万回も実行されるループ内の割り当てを取り除く、または減らす
- C、C++、Java、Go、C#、Rust、Zig、OCamlなどがプロファイラ中心のアプローチの例として挙げられている
- ホットパスの1行を変えただけでも再び割り当てが入り込む可能性があり、その原因を見つけるにはプロファイラに戻る必要がある
- Zigや最近のRustの一部では、allocatorを関数に渡さない方式によって回帰を減らせるが、これは慣例に依存している
[@zero_alloc]の違いは、事後分析やチームのルールではなく、ビルド時点でコンパイラが割り当て回帰をブロックする点にある
1件のコメント
Lobste.rs のコメント
Zig で allocator を関数の引数として渡す理由は、引数として allocator を受け取らない関数が ヒープ割り当てをできないようにするためだと理解していたのですが、合っていますか?
「規約は無視され得る」という話が本当に正しいなら、意図とは違うように思います
関数の中でも外と同じように allocator を新しく作り出せます
ギフトリンク: https://theconsensus.dev/p/2026/…
nogcがあり、GC が割り当てを担うという点で似たセマンティクスを持つと思いますhttps://dlang.org/phobos/dmd_nogc.html
以前、実験的に nogc 例外も追加されましたが、現在の状態や実装は確認していません
https://dlang.org/changelog/2.079.0.html#dip1008
Rust で core だけを使うのも、割り当てを避ける方法の一つです
そのアプローチは結局、「依存関係を管理せよ」あるいは「呼び出しグラフ全体を手作業で確認せよ」に近いものです
記事の焦点は、コンパイラのような ツールがある性質を静的に強制し、その性質が破れる箇所を生産的に見つけ出す ワークフローを提供する方法にあります
D には
@nogcがあり、その次は明確な 割り当てパターンを持つ、直接制御可能な抽象化だけを使うという問題になります記事タイトルを説明的に付ける能力を失ってしまったようなので補足すると、要点は関数に
[@zero_alloc]を付け、その関数の 呼び出しツリーがヒープに触れたらコンパイラがプログラムを拒否する機能ですこうした方式が「例外を投げない/パニックしない」、「ロックなし」、「常に終了する」といった複数の条件にも適用できるのか気になります