世界を動かす技術を、日本語で。

Rustコンパイラの性能

概要

Rustのコンパイル時間が長いことは、ユーザーの間で頻繁に指摘される問題。 Rustプロジェクトはコンパイラ性能改善に継続的に取り組んでいる現状。 過去数年でビルド速度は確実に向上しているが、依然として十分ではないとの声も多い。 技術的・運用的な制約が、さらなる高速化の障壁となっている実態。 今後も新たなアプローチや大規模な変更での改善が期待される状況。

Rustコンパイル時間への不満と現状

  • Rustの コンパイル速度フィードバックループの遅さ は、ユーザーから最も多く寄せられる不満点。
  • Rust関連の ポッドキャスト、ブログ、アンケート、カンファレンス など、様々な場で頻繁に議論される話題。
  • Rustプロジェクト内でも パフォーマンス改善 を重視し、 毎週のトリアージベンチマークスイートの運用 を実施。
  • パフォーマンス向上PR は積極的に歓迎し、 退行があれば即座に修正やリバート を行う体制。
  • x64 Linux 向け最適化に特に注力しているため、他プラットフォームでは効果が限定的な場合も。

コンパイル性能の進歩と現状の課題

  • 2022年から2025年にかけて、 hyperqueue プロジェクトのビルド時間は 約2倍高速化
    • 例:1.61.0(2022年)で26.1秒→1.87.0(2025年)で14.7秒
  • ただし、依然として 多くの開発者にとって生産性のボトルネック である現状。
  • C++開発者 は慣れているが、 Python開発者 などには特に遅さが際立つ印象。
  • 「根本的な解決は可能か?」 という問いに対し、 インクリメンタルビルド の最適化などで十分な高速化が理論上は可能と考察。
  • モノモルフィゼーション、型システム、ビルドスクリプト など、Rust特有の複雑さが高速化の障害。

取り組みと技術的な制約

  • パラレルフロントエンド、代替コード生成バックエンド、より高速なリンカの採用 など、多様なアプローチを模索。
  • MIR-only rlibs、-Zhint-mostly-unused、スマートなインクリメンタルコンパイル など、現時点で利用可能な最適化も存在。
  • ビルドパフォーマンス改善 はRustコミュニティ全体に恩恵をもたらし、 CIやテスト実行時間短縮 にも寄与。

なぜ進捗が遅いのか

  • 技術的な理由

    • rustcは巨大かつ複雑なコードベース で、全体を把握している開発者はほぼ不在。
    • マイクロ最適化の余地は減少 し、既存の最適化は局所的な最小値に達しつつある状況。
    • 最適化によるトレードオフ (例:新しいCPU命令セット対応による互換性問題、メモリアロケータ変更によるメモリ消費増加)。
    • 複数バージョン配布のコスト増大 や、CIリソースの逼迫。
  • 運用・組織的な理由

    • 大規模な変更にはチーム内の合意形成(Major Change Proposal)が必要
    • 下層の変更が全体に波及し、多数のテストやレビューが必要
    • 他の開発と競合しやすく、長期的なメンテナンスコストも高い

今後の展望とアプローチ

  • 特定のワークフロー改善 (例:Relink, don’t rebuild提案)による生産性向上の可能性。
    • Cargoとの連携強化や、無駄な再ビルド回避 など、賢いビルドプロセスの実現。
  • 大規模リファクタリング・構造改革 による本質的な高速化も模索。
  • 証明的なパフォーマンス向上 を実現しても、エッジケース対応や後方互換性維持が難題。

まとめ

  • Rustプロジェクトは コンパイル性能改善に真剣に取り組んでいる
  • 技術的・運用的な制約 が進捗の遅さにつながっている現実。
  • 新たな最適化手法や構造改革 による今後の改善に期待が寄せられる状況。

Hackerたちの意見

LLVMをスキップしてJIT最適化を優先することにどれだけの価値があるのか気になるな。リリースビルドでは、そこそこ最適化されつつデバッグしやすさを保てるなら、いい代理になるかもしれない。JVMを最初のターゲットにするのも面白いかもね、彼らのJITは成熟してて強力だし。

現代の言語でこれがあったら最高だな。開発ビルドでは、JITコンパイルはデバッグビルドよりも良い選択だと思う。最終的にピークパフォーマンスに達する可能性があるからね。ゲームのようなパフォーマンスに敏感なものでは、すべての最適化をオフにしてゲームが使えなくなることなく、良いフィードバックループを保つことが本当に重要だよ。AOTの静的バイナリはデプロイメントにとって価値がある。Rustのような既存の言語で開発するのがどれだけ高コストになるかは全く分からないけど。

JVMはRustにとってあまり意味のあるターゲットじゃないね。Cみたいなフラットなメモリアドレスやポインタ演算を使わないから。Javaのオブジェクトやフィールドがそれぞれ小さなメモリセグメントやアドレス空間にあるみたいな感じ。これのおかげで、GCにはほとんど透明になるのがJavaの大事な特性なんだけど、逆に言うとC系の言語をJVMにコンパイルするのは、JVMのバイト値の配列として「メモリ」を再実装することが多いんだよね。

LLVMをスキップしてJIT最適化されたリンクを使うことにどれだけの価値があるのか気になるな。リリースビルドでは、適切に最適化されてデバッグしやすさを保ちながら、合理的なプロキシを得られるかもしれない。Rustは今、craneliftバックエンドを構築中なんだ。Craneliftは元々JITコンパイラとして作られた。これがデバッグビルドのコンパイラになればいいなと思ってる。 https://github.com/rust-lang/rustc_codegen_cranelift

LLVMの最適化は、Felderaでのコンパイルボトルネックの大部分を占めてるんだ。ここで直面したいくつかの課題についてブログに書いたよ: https://www.feldera.com/blog/cutting-down-rust-compile-times... 将来的にはこの問題を避けるためにJITを構築する必要がほぼ確実にあるね。

大規模なC++コードベースで働いてきたから、長いコンパイル時間には慣れてるんだけど、C++の開発者がRustを嫌うことにここまでこだわるのが意外だな。

これは常に指摘できる定量的なマイナスだよね。もちろん、正当化に使われることもあるけど。

C++にはコンパイル時間を短縮するためにできることがたくさんあるけど、Rustではできないことも多いよ。

C++の開発者がRustを嫌うことにここまでこだわるのが意外だな。人は自分の現在の問題を誇張することがあるから、まるでそれが唯一の重要なことのように聞こえるよね。別のプロジェクトでは「これが商業的な代替品を使い続ける唯一の理由だ」とか、「これが広範な採用を妨げている唯一の要因だ」とか言われたこともある。一方で、私には基本的な完成度を持たせるために必要な優先事項のリストがある。パフォーマンスに関しては、誰にとっても十分なものにはならないよ。常に利用可能なリソースを消費する大きなプロジェクトがあるし、変わったやり方にこだわる人もいる(有効かもしれないけど、非常に典型的ではない)。改善のリクエストは、通常のものと区別がつかないことが多いね。

それ、めっちゃ納得!十分なC++の経験がある人なら、誰でも一度はその悪夢に直面したことがあるよね。できることなら、二度と経験したくないよ。

毎日大規模なC++コードベースで作業してるんだけど、i9で128GBのRAMとNVMeドライブを使っても、コンパイルに30分かかることもある。Rustのコンパイル時間はまだ信じられないくらい遅いよ。少し前に「小〜中規模」のオープンソースプロジェクト[0]に貢献して、使ってるときに出た問題をいくつか修正したんだ。このプロジェクトは、日常のプロジェクトの約3桁小さいから、数千行のRustコードのクリーンビルドに10分近くかかった。プロジェクトへのインクリメンタルな変更も、その時は1分近くかかってた。Rustで5百万行以上のプロジェクトには参加したことがないけど、どれだけ時間がかかるか想像もつかないよ。逆に、同じくらいのサイズのGolangプログラム[1]にいくつかパッチを提出したけど、そのプロジェクトをクローンして依存関係をインストールしてクリーンビルドする方が、Rustプロジェクトの1ファイル変更よりも早かったよ。[0] https://github.com/getsentry/symbolicator [1] https://github.com/buildkite/agent

そこでは、日常のコーディングのために小さなスタンドアロンの実行可能ユニットテストセットとシミュレーションを書くことが答えだったよ。QTやBoostのようなテンプレート重視のものを避けるのも助けになるね。

言語設計の段階でコンパイラのパフォーマンスを考慮しなきゃいけない。言語がある程度の規模に達したら、優先事項でない限り修正はほぼ不可能だよ。最近ここで見た観察によると、最適化で2倍のパフォーマンス向上はよくあるけど、10倍の改善にはアーキテクチャの再設計が必要なんだって。Rustはコミュニティに大きな亀裂を生むことなく再設計するのは難しいだろうから、コンパイルは常に遅いままだと思う。

言語設計とコンパイラアーキテクチャを混同してるよ。それに大きなパフォーマンス向上を得るためにコンパイラを改良するのは難しいし、再アーキテクチャは役立つけど、言語自体を変える必要は必ずしもないんだ。Roslyn(C#)がその最良の例だね。ただし、それは大規模な取り組みで、実現するにはかなりの資金が必要だよ。

簡単に達成できる高パフォーマンスコンパイルを妨げる言語機能を考えることは確かに可能だね。Rustに存在する言語機能(特に、モノモルフィックなジェネリクス)は、そのコンパイル時間のコストに関わらず省略されることは考えられなかった。なぜなら、それはRustの他の目標を損なうことになるから。

コンパイル時間がひどい理由の一つは、すべての依存関係を各プロジェクトごとにコンパイルしなきゃいけないからだよね。20個のプロジェクトが同じ依存関係を使ってる?それぞれ再コンパイルしなきゃならない。これは、ライブラリを動的にロード可能なモジュールとしてコンパイルするための適切なABIがない言語の影響で、ソフトウェアの配布を完全に地獄にするような他の問題も引き起こすんだ。

これは、Dartが以前のマクロの試みをキャンセルした大きな理由だったと思う。Flutterの開発には高速なコンパイルが不可欠で、Dartの使用率のかなりの部分を占めてるから、確か2年以上開発してたのに、そのマクロのバージョンはホットリロードが遅くなるから結局進まなかったんだよね。その冷静さと配慮は、尊敬に値すると思う。

真のチャンピオンだね。

2021年にRustに貢献し始めたとき、私の主な関心はコンパイラのパフォーマンスだった。だから、最初は最適化作業を始めたんだ。それから、コンパイラのベンチマークスイートがメンテナンスを必要としていることに気づいて、そこにも手を付け始めた。さらに、コンパイラ自体をもっと多くの最適化でコンパイルしていないことに気づいて、LTO/PGO/BOLTのサポートを追加する作業を始めた。それがさらにCIインフラの改善につながったんだ。CIのワークフローを待つ時間がかなり長いことにも気づいて、最適化に取り組み始めた。それからRust Annual Surveyを実施して、GSoCプログラムを始めて、ボットの改善に取り組んで、そして…

正しい「継続的改善」について話そうぜ。

正直、この人がヤクオーバーフローを経験しちゃうんじゃないかと心配してる。

記事とは関係ないけど、Rustを何年も使ってきたけど、やっぱり面倒くさい。OS開発や高頻度取引、医療機器、車両ファームウェア、金融ソフトウェア、デバイスドライバーの開発には良い選択肢かもしれないけど、他の一般的な分野にはオーバースペックに感じる。一方で、ZigとGoは週末で学んだんだけど、ほぼ同じくらい速く動いて、メモリの問題も(JavaやC++ほどは)抱えてないんだよね。

このコメントは、なぜそうなるのかの説明があればもっと役立ったと思う。言語、ツール、ライブラリエコシステム?それとも他の何か?

Goが話題に出るたびにRustを擁護するあの人になりたくないけど… Rustはメモリ安全性だけじゃなくて、もっと大きなクラスのエラーから守ってくれるんだ。例えば、イテレータをイテレート中に無効にすることは不可能だし、未設定や無効な値を参照したり、変数を浅くコピーしたり、ミューテックスのロック/アンロックを忘れたりすることもないんだよ。

あなたが遭遇した4つの言語のメモリ問題について、もう少し詳しく教えてくれない?

このベンチマークでは、コンパイラは3年前のほぼ2倍の速さになっている。 公の認識の問題の原因は、Wirthの法則の変種かもしれないね。平均的なコードベース(とその依存関係)のサイズが、コンパイラの改善よりも早く成長しているのかも?

確かに、依存関係を含めるとそうなるよね。それに、依存関係ツリーがあるサイズを超えると、特定のタスクのためにあらゆる代替クレートを引っ張ってくることに気づいたよ。例えば、依存関係の一つがminiz_oxideを使ってて、別のがzlib-rsを使ってるとかな。逆に、大半の依存関係のコンパイルはあまり問題にならないけど、並行処理が簡単だからね。いつも最後の数個のクレートとリンクが半分の時間を取るんだよね。

DoDについて言えば、考慮すべき追加の点はコンパイラのコードベースのメンテナンス性だね。 魔法の杖を振って、DoDやSIMDベクトル化、手作りのアセンブリを使って一晩で全部書き直したと想像してみて。 それは(おそらく)かなり速くなるだろうけど、私たちは即時のパフォーマンスだけでなく、長期的な改善を行う能力も大事にしているんだ。これは著者の不幸な誇張だね。DoDと「手作りのアセンブリ」の間には大きな距離があって、メンテナンス性の議論を正当化するために同じバケツに入れるのは、Rustプロジェクトがユーザーのためにより良いコンパイラを作る能力を傷つけるだけだと思う。ソフトウェアのメンテナンス性を高めるのに何が役立つか知ってる?より速い開発ループだよ。Zigはこれに数年を投資して、ユーザーもコアチームもその成果を楽しみ始めているんだ。 もちろん、みんな自分の優先順位を選ぶ自由はあるけど、その理由には欠陥があると思うし、最終的にはRustプロジェクトがコンパイラのパフォーマンスをもっと優先する方が良いと思う。

いや、でもZigだよ。RustはCを書きたいけど、もっと簡単にしたいときに使うもので、ZigはCより難しくしたいけど、実行やメモリ管理をもっとコントロールしたいときに使うんだよね。

「手作りアセンブリ」は、DoDも含まれるリストの一項目だったよ。あなたはその文を読みすぎてると思う。主張は、DoD自体がコードベースの保守性にも影響を与えるってことなんだ。

最近、複雑なコンパイル時の型構築を使ったZigプロジェクトに取り組んでたんだけど、0.13から最新の開発版にアップデートしたら、この分野での改善がすごいことに驚いた。ほんと、すごく早いイテレーションサイクルに感謝してる。

記事自体はいいし、良いポイントもたくさんあるけど、主要な問題を避けようとしてる感じがする。だからここで言わせてもらうけど、遅さの主な原因はLLVMだよ。

多くのユースケースではそうだけど、コード生成バックエンドとは別のことでボトルネックになっているクレートもあるよね。でも、それがポイントじゃないと思う。LLVMを排除して他のバックエンドを使うこともできるし、他の改善もできる。大事なのは、他にも優先順位があって、進捗を早めるための人手が足りないってことだね。

これはある程度真実だけど、ちょっと誤解を招く部分もある。多くの問題はRustがどう関わっているか、Rustプロジェクトがどう構成されているかから来てるんだ。最終的にはLLVMでの時間として現れるけど、LLVMだけが原因ではないよ。

「誰に聞くかによるけど、例えばC++の開発者の中にはRustのコンパイル時間を全然気にしない人もいる。彼らは同じ(またはそれ以上の)ビルド時間に慣れてるから」 そうだね、ほぼその通り。実際にかかる時間とコンパイルベンチマークを考えると、C++はもっとひどいよ。私が見たり働いたりしたほとんどのC++プロジェクトでは、ツールチェーンにコードジェネレーターが一つ以上あって、すごく遅くなってた。clang-tidyを加えるとさらに悲惨になるよ。小さなプロジェクトでもリントに5分くらいかかることもあるし。Rustで作業してると、ツールチェーン全体(とランゲージサーバー)の速度は本当にありがたい!

クランタイディを加えると、さらに厳しくなるね。小さなプロジェクトでもリントに5分くらいかかることもあるし。サニタイザーを使ってすべてのテストを実行するのも、コンパイル時にRustが除外するものをランタイムでチェックするために必要なんだ。コンパイル時間が速いからRustが大好きなんだけどね。

Rustのエコシステムは、コンパイラが速くなるよりも早く遅くなってる気がする。ライブラリは機能を追加するために成長して、依存関係も増えていく。個々の成長はそれほど悪くないし、機能やプラットフォームのサポートが広がることで正当化されるけど、全体で見ると積み重なっていくんだ。特に、依存関係がさらに依存関係を生むのは倍増効果がある。何年も前にこのことについての投稿を書き始めたけど、結局完成しなかった。固定されたRustコンパイラを使っていたいくつかの遅いプロジェクトを取り上げて、コンパイラと依存関係を最新バージョンに更新したら、必然的にすべてのコンパイルが遅くなったんだ。コンパイラの更新だけで速くなったのにね!

よくわからないけど、こういうことは波があるよね。最近、高依存プロジェクトの構造やコンパイル時間に対する反発が少しあって、ニッチなクレートであるunsynnも、比較的重いsynクレートの代替として注目を集めているみたい。