HN 日本語サマリー

← 一覧へ戻る
プログラミング

GleamがErlangソースコードへのコンパイルを停止

Gleam doesn't compile to Erlang source anymore (gleam.run)

28 pointsby ingve4 コメント

要約

プログラミング言語Gleamがバージョン1.19.0で、Erlangコードジェネレーターを大幅に更新した。以前はErlangソースコードを生成していたが、今後はErlangの抽象構文木(abstract forms)を生成するようになる。これにより、コンパイルパフォーマンスが向上し、デバッグ時のスタックトレースにおけるソースコードの行番号精度が向上した。また、JavaScriptターゲットのコード生成も改善され、パターンマッチングの最適化により、より効率的なJavaScriptコードが生成されるようになった。

全文翻訳

Gleamは、Erlang仮想マシンとJavaScriptランタイム向けの型安全でスケーラブルな言語です。本日、Gleam v1.19.0が公開されましたので、新機能について説明します。 新しいコンパイルターゲット 過去数ヶ月にわたり、Giacomo CavalieriはGleamのErlangコードジェネレーターを完全に書き直し、全く異なる設計を採用し、最も注目すべきは異なるフォーマットを出力するようになったことです。以前はGleamはErlangソースコードを生成していましたが、今後はErlang抽象構文木(abstract forms)を生成します。 「Erlang抽象構文木」とは、Erlangコンパイラが使用する中間表現です。これはメタデータで注釈付けされたツリーであり、Erlang構文を表します。通常、Erlangのトークナイザーとパーサーを実行することで生成されます。Erlangの外部タームフォーマットを使用したバイナリエンコーディングがあり、このバイナリフォーマットを使用することで、生成されたコードを直接ロードでき、Erlangコンパイラのフロントエンドをスキップできます。 この新しいErlangコードジェネレーターは、いくつかの利点をもたらします。 コンパイラのパフォーマンスが向上し、Erlang上で実行されるGleamプロジェクトのビルド時間が大幅に短縮されました。 ランタイムで利用可能な位置メタデータが、コンパイラが生成するErlangソースコードではなく、元のGleamソースコードに対して正確になりました。これにより、例えばBEAMクラッシュレポートやスタックトレースの行番号が完全に正確になり、以前は最も近い関数を指すだけで不正確になる可能性がありました。このメタデータは、edbのようなデバッガでのGleamの完全なサポートを可能にする可能性もありますが、私たちはまだこの作業を行っていません。 Gleamコンパイラのコード品質が向上しました。Erlangコードジェネレーターは、Gleamコードベースの中で最も古く、最も安定した部分の1つでした。問題を引き起こしていたわけではありませんが、今日の基準や慣習に準拠していませんでした。この新しい置き換えは優れており、コンパイラ全体としての基準を引き上げていると言えます。私たちはもう誰かが「トランスパイラ」という言葉を軽蔑的に使うのを聞く必要がなくなりました。 さて、どれくらい速いのでしょうか?すぐに数字をお見せしますが、ベンチマークは常に人工的であり、物語のすべてを伝えることは決してないことを覚えておいてください。このデータは良い紹介または出発点となるかもしれませんが、良い理解には、個人がさらなる調査と経験を行う必要があります。 ベンチマークは、José Valim氏のlangcompilebenchプロジェクトに基づいています。Joséさん、ありがとうございます!これは、それぞれが「hello world」文字列を返す100個の関数を含む100個のモジュールをコンパイルするのにかかる時間を測定したものです。このテストプロジェクトの形状は、異なる言語間で簡単に再現でき、最も公平なテストプロジェクトを作成できるため実用的ですが、コンパイルされる各言語の機能のサブセットが小さいという点で限界があります。実際のプロジェクトでは、コードははるかに多様になり、異なる機能は異なる言語で異なるコンパイルコストを持ちます。 コードジェネレーターのリライトの最初の段階は、前のリリースであるv1.18.0でリリースされたため、v1.17.0と新しくリリースされたv1.19.0を比較してみましょう。このチャートは、ベンチマークプロジェクトのコンパイルにかかる時間を示しています。値が低いほど良いです。 0ms 100ms 200ms 300ms 400ms 500ms Gleam v1.19 Gleam v1.17 ご覧の通り、かなりの改善です!これはキャッシュなしの完全なビルドです。Gleamのコンパイルはインクリメンタルであるため、通常開発中は、プロジェクト全体をコンパイルしないため、はるかに高速になります。 元のlangcompilebenchにはErlang、Elixir、Gleamしか含まれていませんが、他の人気のあるプログラミング言語をいくつか追加して、Gleamが馴染みのある言語と比較してどれだけ速くコンパイルされるかの大まかな感覚を得られるようにしました。JavaScriptにコンパイルする際のGleamも含まれています。結果は以下の通りです。 0ms 200ms 400ms 600ms 800ms 1000ms Gleam (JavaScript) Go Gleam (Erlang) Erlang Java Elixir Elm Rust C# TypeScript 7 覚えておいてください:これは人工的なベンチマークであり、これだけではこれらの言語について確固たる結論を導き出すには不十分です。とはいえ、これらの結果はGleamのコンパイルが非常に高速であることを示唆しており、Gleamプログラマーとして、コンピュータを待つ時間がほとんどなく、Gleamの開発は非常に楽しいと言えます。 なぜBEAMバイトコードを直接ターゲットにしないのか? ソースコードをErlangコンパイラに渡すコンパイルから、Erlangコンパイラに渡される中間表現に移行しましたが、なぜErlangコンパイラを完全にバイパスしないのでしょうか?Gleamのバイトコードジェネレーターを作成してErlangコンパイラを上回ることはできないでしょうか?おそらくGleamの型情報を使用して、より最適化されたコードを生成することもできるでしょう。 これらの利点を達成することは可能ですが、可能性は低いでしょう。ErlangソースおよびErlang抽象構文木とは異なり、BEAMバイトコードは固定されていません。仮想マシンの新しいリリースごとにバイトコードを進化させ、改善し、新しい機能を追加し、時には冗長になった機能を取り除くことができます。私たちはこの進化に常に追いつくことを約束し、Erlangメンテナーと緊密に協力して今後の変更に備え、仮想マシンの新しいリリースに対応した新しいバージョンのGleamを用意する必要があります。また、Gleamのより強力な静的解析の助けを借りても、数十年にわたってErlangコンパイラに実装されてきた既存の最適化をすべて再現するにはかなりの労力が必要です。 Gleamはスポンサーシップによってサポートされているコミュニティプロジェクトです。私たちは、企業や学術機関に裏打ちされた言語の財政のほんの一部しか持っていないため、リソースを最も効率的かつ持続可能な方法で使用する方法を慎重に検討する必要があります。Gleamはソフトウェア開発のための信頼できる基盤であり、私たちが下すすべての決定は、今後数年から数十年先まで有効である必要があります。Erlang抽象構文木へのコンパイルは、今日のGleamにとってコストとメリットのスイートスポットです。私たちはこの決定において素晴らしい仲間にも恵まれています。私たちの愛されている年上の言語であるElixirも、抽象構文木を介してErlangにコンパイルされます。Elixirにとって十分であれば、Gleamにとっても十分です! さて、それについては十分です。Gleam v1.19.0には、さらに多くの機能があります。 JavaScriptの決定木代入最適化 Erlangコード生成だけでなく、JavaScriptにもいくつかの良い改善があります。Gleamでは、フロー制御はcase式を使用したパターンマッチングで行われ、ネストしたif文にコンパイルされます。パターンマッチングは宣言的なので、コンパイラはランタイムロジックを並べ替え、最適化し、分割統治アプローチを使用して可能な限り迅速に正しいブランチを見つけることができます。John Downey氏は、このプロセスを改善して、よりフラットなコードを生成し、ネストしたif文を単一の条件に折りたたみ、中間変数を減らしました。 例えば、このGleamコードを見てみましょう。 pub fn go(x) { case x { Wibble(1, 2) -> 1 _ -> 2 } } 以前は、この小さなGleamコードは、驚くほど大きなJavaScriptコードにコンパイルされていました。 export function go(x) { if (isWibble(x)) { let $ = x[0]; if ($ === 1) { let $1 = x[1]; if ($1 === 2) { return 1; } else { return 2; } } else { return 2; } } else { return 2; } } しかし今では、次のように生成されます。 export function go(x) { if (isWibble(x) && x[0] === 1 && x[1] === 2) { return 1; } else { return 2; } } 素晴らしい改善だと思います!驚くべきことに、コードバンドルのサイズには、ミニファイおよび圧縮後(gzipは本当に魔法です)にほとんど、あるいは全く変化がありませんが、生成されたコードはJavaScriptエンジンが最適化するためのブランチが少なくなっています。Johnさん、ありがとうございます! リストリテラルの最適化 それぞれの言語で構文は似ていますが、Gleamの不変な永続リスト型は、JavaScriptの可変な連続配列型とは異なります。GleamコードがJavaScriptにコンパイルされるとき、リストリテラルはランタイムデータ構造を構築するJavaScriptコードにコンパイルされる必要があります。 例えば、このGleamコードを見てみましょう。 let numbers = [1, 2, 3] これは、JavaScript配列が構築され、渡されるようなJavaScriptコードにコンパイルされます。