1744891003
2025-04-17 11:22:00
錆は実行時に速くなりますが、コンパイル時にはそれほど多くはありません。それは、深刻な錆のコードベースに取り組んでいる人にとってはほとんどニュースではありません。数秒のシェービング専用のブログ投稿のジャンルがあります cargo build。
Felderaでは、テーブルとビューを定義するためにユーザーにSQLを書き込ませます。ボンネットの下で、そのsqlを錆コードにコンパイルします。 rustc 単一のバイナリに 徐々に 新しいデータがテーブルにストリーミングされるように、すべてのビューを維持します。
過去にはすでに多くのトリックを引き出して、コンピレーションをスピードアップしました。つまり、消去、積極的なコード重複排除、コードゲンラインの制限です。そして、それは私たちをかなり遠くに連れて行きました。しかし、最近、私たちはかなり複雑なSQLを備えた新しい大規模なエンタープライズクライアントのボーディングを開始し始めました。彼らはフェルデラと一緒に非常に大きなプログラムを多く書きました。たとえば、そのうちの1つは8562行のSQLコードであり、最終的にはFeldera SQL-to-Rustコンパイラによって〜100kの錆コードに翻訳されました。
明確にするために、これは私たちがコンパイルしている巨大なモノリスではありません。私たちは、生成された錆の〜100kラインについて話している。それは、Linuxカーネルのようなものと比較してピーナッツです – 4,000万本のライン(数分でコンパイルすることができます)。
それでも…この1つのプログラムは、私のマシンをコンパイルするのに約25分かかりました。さらに悪いことに、お客様のセットアップでは約45分かかりました。そして、これは私たちがすでに生成されたコードを切り替えた後でした 動的派遣を使用して、すべての単層をほぼ排除しました。
これがFelderaマネージャーのログです。
[manager] SQL compilation success: pipeline 0196268e-7f98-7de3-b728-0ee339e449fa (program version: 2) (took 101.94s)
[manager] Rust compilation success: pipeline 0196268e-7f98-7de3-b728-0ee339e449fa (program version: 2) (took 1617.77s; source checksum: cbffcb959174; integrity checksum: 709a17251475)
ほとんどすべての時間に錆びを編集するのに費やされています。 SQLからラストへの翻訳には約1M40がかかります。さらに悪いことに、さびビルドは release ビルドインします cargo、したがって、それは毎回ゼロから起こります(ここで与える時間にすでにキャッシュ/再利用されている貨物箱の依存関係を除く)。入力SQLの最も小さな変更でさえ、その巨大なプログラムの完全な再構築を開始します。
もちろん、デバッグビルドも試してみました。それらは時間を約5分に減らしましたが、実際には使用できません。お客様は実際のランタイムパフォーマンスを気にします。SQLコードタイプチェックが既にRustコードが正常にコンパイルされ、リアルタイムデータパイプラインを実行しており、エンドツーエンドのレイテンシとスループットを確認したいと考えています。デバッグビルドは遅すぎて誤解を招きます。
何が起こっていますか?
これがイライラする部分です。
私たちは使用しています rustc v1.83、そして128個のスレッドを備えた64コアマシンを持っているにもかかわらず、Rustはほとんど動作しません。これは、見るとすぐに明らかになります htop コンプライレンス中:
それは正しい。 100%のコアが1つ、残りは眠っています。
私たちは通り過ぎてこの木枠の編集を器具にすることができます -Ztime-passes に RUSTFLAGS
(これには毎晩の再コンパイルが必要です)。それは、大部分の時間がLLVMパスとコードゲンで費やされていることを明らかにしています。
time: 0.346; rss: 38MB -> 342MB ( +304MB) parse_crate
time: 0.000; rss: 344MB -> 345MB ( +1MB) crate_injection
time: 5.286; rss: 345MB -> 1607MB (+1262MB) expand_crate
time: 5.287; rss: 345MB -> 1607MB (+1262MB) macro_expand_crate
time: 0.091; rss: 1607MB -> 1607MB ( +0MB) AST_validation
time: 0.002; rss: 1607MB -> 1608MB ( +1MB) finalize_imports
time: 0.029; rss: 1608MB -> 1608MB ( +0MB) finalize_macro_resolutions
time: 2.382; rss: 1608MB -> 1937MB ( +329MB) late_resolve_crate
time: 0.071; rss: 1937MB -> 1938MB ( +1MB) resolve_check_unused
time: 0.138; rss: 1938MB -> 1938MB ( +0MB) resolve_postprocess
time: 2.627; rss: 1607MB -> 1938MB ( +331MB) resolve_crate
time: 0.069; rss: 1940MB -> 1940MB ( +0MB) write_dep_info
time: 0.070; rss: 1940MB -> 1940MB ( +0MB) complete_gated_feature_checking
time: 0.217; rss: 2790MB -> 2651MB ( -139MB) drop_ast
time: 3.361; rss: 1940MB -> 2353MB ( +414MB) looking_for_entry_point
time: 3.961; rss: 1940MB -> 2346MB ( +407MB) misc_checking_1
time: 6.301; rss: 2346MB -> 2007MB ( -339MB) coherence_checking
time: 44.158; rss: 2346MB -> 3061MB ( +714MB) type_check_crate
time: 18.773; rss: 3061MB -> 5024MB (+1963MB) MIR_borrow_checking
time: 4.650; rss: 5024MB -> 5241MB ( +217MB) MIR_effect_checking
time: 0.360; rss: 5243MB -> 5255MB ( +12MB) module_lints
time: 0.360; rss: 5243MB -> 5255MB ( +12MB) lint_checking
time: 0.947; rss: 5255MB -> 5254MB ( -1MB) privacy_checking_modules
time: 1.587; rss: 5241MB -> 5254MB ( +13MB) misc_checking_3
time: 0.259; rss: 5254MB -> 5249MB ( -5MB) monomorphization_collector_root_collections
time: 54.766; rss: 5249MB -> 7998MB (+2749MB) monomorphization_collector_graph_walk
time: 6.086; rss: 8010MB -> 8565MB ( +554MB) partition_and_assert_distinct_symbols
time: 0.000; rss: 8414MB -> 8415MB ( +1MB) write_allocator_module
time: 35.220; rss: 8415MB -> 18037MB (+9622MB) codegen_to_LLVM_IR
time: 96.733; rss: 5254MB -> 18037MB (+12783MB) codegen_crate
time: 1333.423; rss: 10070MB -> 3176MB (-6893MB) LLVM_passes
time: 1303.074; rss: 13594MB -> 756MB (-12837MB) finish_ongoing_codegen
time: 1.091; rss: 756MB -> 756MB ( +0MB) run_linker
time: 0.105; rss: 755MB -> 755MB ( +0MB) link_binary_remove_temps
time: 1.217; rss: 756MB -> 755MB ( -1MB) link_binary
time: 1.218; rss: 756MB -> 754MB ( -2MB) link_crate
time: 1.218; rss: 756MB -> 754MB ( -2MB) link
time: 1483.483; rss: 26MB -> 514MB ( +487MB) total
これらの30分間に、Rustがいくつかのスレッド(3〜4)がスピンアップすることもありますが、マシンを完全に利用することはありません。近くにさえありません。

私はそれを手に入れます:並行する編集は難しいです。しかし、これはいくつかのエッジケースではありません。自分自身を見ると、このプログラムで編集を並行するのに十分な機会を明らかに見ました。
脇に注意してください:あなたは増加についてどう思いますか codegen-units で Cargo.toml?それはこれらのパスをスピードアップしませんか?私たちの経験では、それは重要ではありませんでした:それはデフォルトに設定されました 16 報告された時間については、次のような値も試しました 256 デフォルトのLTO構成(薄いローカルLTO)を使用します。それはやや混乱していました(非非 rustc 専門家)。これの説明を読みたいです。
私たちはそれについて何ができますか?
すべてを含む1つの巨大な木枠を放出する代わりに、SQLからラストコンパイラを微調整して、出力を多くの小さな箱に分割しました。それぞれがロジックの一部のみをカプセル化し、互いにきちんと依存して、単一のトップレベルで互いにきちんとしています main クレートはそれらをすべて引き込みます。
結果は壮観でした。コンピレーション中の変更後の同じHTOPビューは次のとおりです。

美しい。すべてのCPUが常に完全に利用されています。
そしてそれは示しています:さびプログラムをコンパイルする時があります 2m10s!
[manager] Rust compilation success: pipeline 01962739-79fd-7f03-bbf2-f8e29ce21e1d (program version: 2) (took 150.24s; source checksum: 0336f3eb9dc1; integrity checksum: 6051bcde6674)
どのように修正しましたか?
ほとんどの錆プロジェクトでは、数十(または数百)の木枠にロジックを分割することは、せいぜい非現実的であり、最悪の場合は悪夢です。しかし、私たちの場合、それは驚くほど簡単でした – フェルデラがボンネットの下でどのように機能するかのおかげで。
ユーザーがFelderaにSQLを書き込むと、データフローグラフに翻訳します。ノードはデータを変換する演算子であり、エッジはそれらの間のデータの流れを表します。このようなグラフの小さな断片は次のとおりです。

錆コードはこの構造から完全に自動生成されているため、それを分割する方法を完全に制御できました。
各オペレーターは独自の木枠になります。各クレートは、データフローの特定の部分を構築する単一の関数をエクスポートします。それらはすべて同じ予測可能な形状に従います。トップレベルのメインクレートは、それらを一緒に配線するだけです。
pub fn create_operator_0097dd9de75ffef3(circuit: &RootCircuit,catalog: &mut Catalog,
i0: &Streami32>, Tup5i32, SqlString, F64, F64, Optioni32>>>>,
i1: &Streami32>, Tup0>>,
) -> Streami32, SqlString, F64, F64, Optioni32>>>>{
let operator_0097dd9de75ffef3: Streami32, SqlString, F64, F64, Optioni32>>>> = i0.join(&i1, move |p0: &Tup1i32>, p1: &Tup5i32, SqlString, F64, F64, Optioni32>>, p2: &Tup0, | ->
Tup5i32, SqlString, F64, F64, Optioni32>> {
Tup5::new(
(*p1).0,
(*p1).1.clone(),
(*p1).2,
(*p1).3,
(*p1).4.as_ref().cloned())
});
return operator_0097dd9de75ffef3;
}
これらの木枠の名前を理解する必要があります。シンプルだが強力な方法は、錆びたコードをハッシュし、それを木枠の名前として使用することです。
これにより、2つのことが保証されます。
a。ユニークなクレート名があります。
b。さらに重要なことは、SQLへの増分変更が非常に効果的になることです
ユーザーがSQLコードをわずかに微調整することを想像してください。何が起こるかは、ほとんどのオペレーター(およびその木枠)が同じままであること(ハッシュは変わらない)、そして rustc 以前にまとめられたアーティファクトのほとんどを再利用できます。変更のために追加される新しいコードは、新しいクレートを生成することになります(異なるハッシュ付き)。
それで、私たちはそのモンスターSQLプログラムのためにいくつの木枠について話しているのでしょうか?
フェルデラコンテナ内のコンパイラディレクトリを覗きましょう。
ubuntu@12e1de52de1b:~/.feldera/compiler/rust-compilation$ ls crates/
feldera_pipe_operator_000cb1599cb60b91 feldera_pipe_operator_4aab3e223e4ddcf9 feldera_pipe_operator_8d1f38d0358deacf feldera_pipe_operator_d8058d2f87a41ca0
feldera_pipe_operator_004093943841ab45 feldera_pipe_operator_4ae3aa1446d98a19 feldera_pipe_operator_8d30ed71269c765f feldera_pipe_operator_d841ffa208faa462
feldera_pipe_operator_004675554aea30aa feldera_pipe_operator_4aff1d1e8d2a6a9a feldera_pipe_operator_8e25b73d54f6491e feldera_pipe_operator_d88bab492aa0c8f5
feldera_pipe_operator_008ba4153ded3848 feldera_pipe_operator_4b3575ba2e10dad3 feldera_pipe_operator_8e667e68984170e5 feldera_pipe_operator_d8a43a536535a38d
feldera_pipe_operator_00bee114a0d5eb4c feldera_pipe_operator_4b5370144b5268ae feldera_pipe_operator_8eb1e7460e7376f9 feldera_pipe_operator_d8c6422350e6e8fe
feldera_pipe_operator_00d71fa11f791e35 feldera_pipe_operator_4b5d1c560b048f22 feldera_pipe_operator_8edfa111c7ed57b6 feldera_pipe_operator_d968b48784b4f7af
...
その後:
ubuntu@12e1de52de1b:~/.feldera/compiler/rust-compilation$ ls crates/ | wc -l
1106
そうです – 1,106箱!
過度に聞こえますか?多分。しかし、最終的にこれが作られています rustc もっと効果的です。
私たちは終わりましたか?
残念ながら、まったくそうではありません。ここにはまだいくつかの謎があります。コンパイル時間全体で128個のスレッドまたは64コアを完全に利用していることを考えると、エンベロープ計算の背面を実行できます。 25 min / 128 = 12 sec (または多分 24 sec ハイパースレッドは実際のコアではないため)。それでもそれはかかります 170s すべてをコンパイルします。もちろん、実際には線形スピードアップを期待することはできませんが、それでも 7x それよりも遅いように見えます(これらはすべて平行しています rustc 独立して実行される呼び出し)。同様の減速は、メモリとコアがはるかに少ないラップトップグレードのマシンでも発生するため、非常に大きなマシンに影響を与えるわけではありません。
ここに何について考えがあります かもしれない 起こりますが、私たちはこれについてもっと意見を聞いてうれしいです:
- ハードウェアリソースに関する競合(システムには十分なメモリがありますが、キャッシュに争うかもしれません)
- ファイルシステムはボトルネックです(RAM-FSでこれを実行しようとしようとしたので、違いはありませんでしたが、カーネルのファイルシステムコードのロックと競合する可能性があります)
- 1Kクレートのそれぞれをコンパイルすると、単一のクレートを使用するときに償却されるいくつかのステップを何度も実行するようになりました(Trueですが、でコンパイルすると減速は発生しません
-j1、個々のクレートコンパイル時間は、順番に発生する限り、はるかに高速です) - リンク1Kクレートを追加してからボトルネックは今ではボトルネックですか?使用します
moldそして、私たちは合計リンク時間が周りにあることがわかります7 sec。
結論
フードの下で錆コードを生成する方法を変更するだけで、ハードウェアと戦うのではなく、フェルデラのコンパイルタイムスケールを使用して作成しました。複雑なエンタープライズスケールのSQLであっても、30〜45分かかったものは3分以内にコンパイルされました。
すでにフェルデラをその限界に押し付けているなら、ありがとう。あなたのワークロードは、私たちがすべての人のためにシステムをより良くするのに役立ちます。
#錆びを減らす時間は1000箱で302分間に