Runtime: 階層型JITを導入する

作成日 2016年04月14日  ·  63コメント  ·  ソース: dotnet/runtime

.NET JITが階層化されていないのはなぜですか?

JITには、起動時間の短縮と定常状態のスループットの向上という2つの主要な設計目標があります。

最初は、これらの目標は対立しているように見えます。 しかし、2層のJIT設計では、両方とも達成可能です。

  1. すべてのコードは解釈され始めます。 これにより、起動時間が非常に速くなります(RyuJITよりも速くなります)。 例: Mainメソッドはほとんどの場合コールドであり、それを実行するのは時間の無駄です。
  2. 頻繁に実行されるコードは、非常に高品質のコードジェネレーターを使用して圧縮されます。 ホットになるメソッドはほとんどありません(1%?)。 したがって、高品質のJITのスループットはそれほど重要ではありません。 Cコンパイラが非常に優れたコードを生成するのに費やす時間と同じくらいの時間を費やす可能性があります。 また、コードがホットであると_想定_することもできます。 クレイジーなループのようにインライン化できます。 コードサイズは問題ではありません。

このアーキテクチャに到達するのはそれほどコストがかからないようです。

  1. インタプリタを書くことは、JITに比べて安いようです。
  2. 高品質のコードジェネレータを作成する必要があります。 これはVCまたはLLILCプロジェクトである可能性があります。
  3. インタプリタされた実行中のコードをコンパイルされたコードに移行できる必要があります。 これは可能です。 JVMがそれを行います。 スタック置換(OSR)で呼び出されます。

このアイデアはJITチームによって追求されていますか?

.NETは、数億台のサーバーで実行されます。 コード生成が最適ではないため、多くのパフォーマンスがテーブルに残っており、何百万ものサーバーが顧客のために浪費されているように感じます。

カテゴリ:スループット
テーマ:ビッグベット
スキルレベル:エキスパート
費用:特大

area-CodeGen-coreclr enhancement optimization tenet-performance

最も参考になるコメント

最近のPR(https://github.com/dotnet/coreclr/pull/17840、https://github.com/dotnet/sdk/pull/2201)では、ランタイム構成として階層型コンパイルを指定することもできます。 jsonプロパティまたはmsbuildプロジェクトプロパティ。 この機能を使用するには、環境変数がしばらく前から存在しているのに対し、ごく最近のビルドを使用する必要があります。

全てのコメント63件

@GSPP階層化は、会話を計画する際の一定のトピックです。 私の印象では、それが慰めを提供するのであれば、それは_if_ではなく_when_の問題であるということです。 まだ存在しない理由については、歴史的に、認識された潜在的な利益が、複数のコード生成モードの複雑さとリスクの増大に対処するために必要な追加の開発リソースを正当化できなかったためだと思います。 でも、本当に専門家に話してもらうべきなので、追加します。

/ cc @ dotnet / jit-contrib @russellhadley

どういうわけか、これがcrossgen / ngen、Ready to Run、corertの世界にまだ関連しているとは思えません。

現在、これらのいずれも高い定常状態のスループットを提供していません。これは、ほとんどのWebアプリにとって重要です。 個人的には起動時間を気にしないので、もしそうなら、私はそれに満足しています。

しかし、これまでのところ、.NETのすべてのコードジェネレーターは、2つの目標の間で不可能なバランスを取ることを試みており、どちらもうまく達成していません。 最適化を11に変えることができるように、そのバランスを取る行為を取り除きましょう。

しかし、これまでのところ、.NETのすべてのコードジェネレーターは、2つの目標の間で不可能なバランスを取ることを試みており、どちらもうまく達成していません。 最適化を11に変えることができるように、そのバランスを取る行為を取り除きましょう。

私は同意しますが、これを修正するのに通訳のようなものは必要ありません。 RyuJITでもLLILCでも、優れたcrossgenコンパイラです。

最大の利点は、実行時にコードを生成する必要があるアプリケーションにとってだと思います。 これらには、動的言語とサーバーコンテナが含まれます。

動的に生成されたコードが1つの動機であることは事実ですが、静的コンパイラーが実行時に利用可能なすべての情報にアクセスすることは決してないことも事実です。 それだけでなく、(たとえばプロファイル情報に基づいて)推測する場合でも、静的コンパイラーがモーダルまたは外部のコンテキスト依存の動作の存在下でそうすることははるかに困難です。

Webアプリはngenスタイルの処理を必要としないはずです。 デプロイメントパイプラインにうまく適合しません。 大きなバイナリをngenするのに多くの時間がかかります(ほとんどすべてのコードが動的にデッドまたはコールドであっても)。

また、Webアプリをデバッグおよびテストする場合、現実的なパフォーマンスを提供するためにngenに依存することはできません。

さらに、動的情報を使用するというキャロルの2番目のポイント。 解釈層は、コード(ブランチ、ループトリップカウント、動的ディスパッチターゲット)をプロファイリングできます。 完璧にマッチします! 最初にプロファイルを収集し、次に最適化します。

階層化は、すべてのシナリオのすべてを永遠に解決します。 おおよそ:)これにより、実際にJITの可能性を得ることができます。Cコンパイラが実行できる以上のパフォーマンスを実現します。

現在のRyuJITの現在の実装は、Tier 1には十分です...問題は、事後に実行できるホットパス用にTier2の極端な最適化JITを使用することは理にかなっているでしょうか。 基本的に、何かがホットであることを知るのに十分なランタイム情報を検出または持っている場合、または最初から代わりにそれを使用するように求められた場合。

RyuJITは、Tier 1になるのに十分な性能を備えています。問題は、インタプリタの起動時間が(私の見積もりでは)はるかに速くなることです。 2番目の問題は、ティア2に進むために、ティア1コードを実行するローカル状態を新しいティア2コード(OSR)に転送できる必要があることです。 それにはRyuJITの変更が必要です。 インタプリタを追加することは、私が思うに、より安価なパスであり、同時により良い起動待ち時間になるでしょう。

さらに安価な方法は、実行中のコードをTier2コードに置き換えないことです。 代わりに、ティア1コードが自然に戻るまで待ちます。 これは、コードが長時間実行されるホットループに入った場合に問題になる可能性があります。 そのようにTier2のパフォーマンスに到達することは決してありません。

それはそれほど悪くはなく、v1戦略として使用できると思います。 メソッドをホットとしてマークする属性などの緩和策が利用可能です(これは、現在のJIT戦略でも存在するはずです)。

@GSPPそれは本当ですが、それはあなたが次の実行でそれを知らないという意味ではありません。 Jittedコードとインストルメンテーションが永続的になった場合でも、2回目の実行でTier 2コードを取得できます(起動時間が多少かかります)。これは、ほとんどサーバーコードを記述しているため、個人的には気にしません。

インタプリタを書くことは、JITに比べて安いようです。

まったく新しいインタープリターを作成する代わりに、最適化を無効にしてRyuJITを実行するのは理にかなっていますか? それは起動時間を十分に改善しますか?

高品質のコードジェネレータを作成する必要があります。 これはVCである可能性があります

Visual C ++バックエンドであるC2について話しているのですか? これはクロスプラットフォームでもオープンソースでもありません。 両方の修正がすぐに行われるとは思えません。

最適化を無効にすることをお勧めします。 ただし、OSRの問題は残っています。 ランタイムが安全なポイントで実行時にILアーキテクチャ状態(ローカルおよびスタック)を導出できるようにするコードを生成することがどれほど難しいかわからない場合は、それをTier 2 Jittedコードにコピーし、Tier2実行を機能の途中で再開します。 JVMはそれを行いますが、それを実装するのにどれだけの時間がかかったかは誰にもわかりません。

はい、私はC2について話していました。 デスクトップJITの少なくとも1つがC2コードに基づいていることを覚えていると思います。 おそらくCoreCLRでは機能しませんが、デスクトップでは機能する可能性があります。 マイクロソフトはコードベースを調整することに関心があると確信しているので、おそらくそれは確かに外れています。 LLVMは素晴らしい選択のようです。 現在、複数の言語がLLVMをGCおよび一般的なマネージドランタイムで動作させることに関心を持っていると思います。

LLVMは素晴らしい選択のようです。 現在、複数の言語がLLVMをGCおよび一般的なマネージドランタイムで動作させることに関心を持っていると思います。

このトピックに関する興味深い記事:Appleは最近、JavaScript JITの最終層をLLVMから移動しました: https ://webkit.org/blog/5852/introducing-the-b3-jit-compiler/。 彼らが遭遇したのと同様の問題に遭遇する可能性があります。コンパイル時間が遅いことと、LLVMのソース言語に関する知識の欠如です。

RyuJITより10倍遅いのは、2番目の層では完全に許容できます。

ソース言語の知識の欠如(これは本当の懸念事項です)がLLVMのアーキテクチャーに固有のものではないと思います。 複数のチームがLLVMをソース言語の知識をより簡単に利用できる状態に移行するのに忙しいと思います。 _All_非C高級言語では、LLVMでコンパイルするときにこの問題が発生します。

WebKIT FTL / B3プロジェクトは、.NETよりも成功するのが難しい立場にあります。これは、コードの実行時に、合計で数百ミリ秒の時間を消費して終了するコードを実行する場合に優れている必要があるためです。 これは、Webページを駆動するJavaScriptワークロードの性質です。 .NETはその場にありません。

@GSPPおそらくLLILCについて知っていると思います。 そうでない場合は、ご覧ください。

私たちはしばらくの間、CLRコンセプトのLLVMサポートに取り組んでおり、EHとGCの両方の改善に投資してきました。 両方でやるべきことはまだかなりあります。 それを超えて、GCの存在下で最適化が適切に機能するように、未知の量の作業があります。

LLILCは停止しているようです。 それは...ですか?
2016年4月18日19:32、「AndyAyers」 [email protected]は次のように書いています。

@GSPPhttps ://github.com/GSPPおそらくLLILCについて知っていると思います
https://github.com/dotnet/llilc。 そうでない場合は、ご覧ください。

私たちはしばらくの間、CLRコンセプトのLLVMサポートに取り組んできました。
EHとGCの両方の改善に投資しました。 まだまだやることがたくさんあります
どちらも。 それを超えて、作業量が不明な場合は最適化を取得します
GCの存在下で適切に動作します。


コメントしたのでこれを受け取っています。
このメールに直接返信するか、GitHubで表示してください
https://github.com/dotnet/coreclr/issues/4331#issuecomment -211630483

@ drbo -LLILCは今のところ後回しになっています-MSチームは、RyuJITでより多くのターゲットを取り上げるだけでなく、CoreCLRがリリースを推進するときに発生する問題の修正に注力してきました。 私たちが(現在)LLILCでどれだけ得たかに基づいて学んだ教訓の投稿を書くことは、私のTODOリスト(私の豊富な自由時間)にありますが、私はまだそれに到達していません。
階層化に関して、このトピックは何年にもわたって多くの議論を生み出してきました。 新しいワークロードのいくつかと、バージョン管理可能なすぐに実行できるイメージが新たに追加されたことを考えると、階層化の方法と場所を新たに検討することになります。

@russellhadley投稿を書くための自由な時間はありましたか?

仮に、スタックスロットとgcrootがプロモートされていないことで、オプティマイズが壊れて、ジッティング時間が遅くなることについて何かがあるはずです...プロジェクトのコードをよく見てください。

また、SelectionDAGに直接ジャンプして、LLVMバックエンドの一部を実行することは可能で有益かどうか疑問に思います。 少なくともいくつかののぞき穴とコピーの伝播...たとえば、レジ​​スタへのgcrootプロモーションがLLILCでサポートされている場合

現在のボトルネックを含むLLILCの状況と、それがRyuJITに対してどのように対処するかについて興味があります。 本格的な「産業用」コンパイラであるLLVMは、OSSで利用できる非常に豊富な最適化を備えている必要があります。 メーリングリストでは、ビットコード形式のより効率的で高速なシリアル化/逆シリアル化についていくつかの話し合いがありました。 これがLLILCにとって有用なことかどうか疑問に思います。

これについてこれ以上の考えはありましたか? @russellhadley CoreCLRがリリースされ、RyuJITは(少なくとも)x86に移植されました–ロードマップの次は何ですか?

これに関する作業の開始については、dotnet / coreclr#10478を参照してください。

また、dotnet / coreclr#12193

@noahfalk 、マネージコード自体からすぐにティア2コンパイルを強制するようにランタイムに指示する方法を提供していただけますか? 階層型コンパイルはほとんどのユースケースで非常に良いアイデアですが、私は起動時間は関係ありませんが、スループット安定したレイテンシーが不可欠なプロジェクトに取り組んでいます。

私の頭のてっぺんから、これは次のいずれかである可能性があります。

  • 構成ファイルの新しい設定、JITに常にティア1をスキップさせる<gcServer enabled="true" />のようなスイッチ
  • または、 RuntimeHelpers.PrepareMethodのようなもので、ホットパスの一部であるすべてのメソッドのコードによって呼び出されます(これを使用して、起動時にコードを事前にJITします)。 これには、ホットパスが何であるかを知っている必要がある開発者により大きな自由度を与えるという利点があります。 このメソッドの追加のオーバーロードは問題ありません。

確かに、これから恩恵を受けるプロジェクトはほとんどありませんが、JITがデフォルトで最適化をスキップするのではないかと心配しており、代わりにコードを大幅に最適化したいとは言えません。

デザインドキュメントに次のように書いていると思います。

マネージコードAPIからアクセスできる新しいビルドパイプラインステージを追加して、自己変更コードを実行します。

これは非常に興味深いように聞こえます😁しかし、私がここで求めていることをカバーしているのかどうかはよくわかりません。


また、関連する質問:2番目のJITパスはいつ開始されますか? メソッドがn回呼び出されるのはいつですか? JITは、メソッドが実行されるはずのスレッドで発生しますか? その場合、メソッド呼び出しの前に遅延が発生します。 より積極的な最適化を実装すると、この遅延は現在のJIT時間より長くなり、問題になる可能性があります。

メソッドが十分な回数呼び出された場合、またはループが発生した場合に発生するはずです
十分な反復を実行します(ステージ上の置換)。 それが起こるはずです
バックグラウンドスレッドで非同期的に。

2017年6月29日19:01、「LucasTrzesniewski」 [email protected]
書きました:

@noahfalk https://github.com/noahfalk 、方法を教えてください
ランタイムに、ティア2コンパイルをすぐに強制するように指示します。
マネージコード自体? 階層型コンパイルは、ほとんどの人にとって非常に良いアイデアです
ユースケースですが、起動時間が関係ないプロジェクトに取り組んでいます
ただし、スループット安定した遅延が不可欠です。

私の頭のてっぺんから、これは次のいずれかである可能性があります。

  • 設定ファイルの新しい設定、次のようなスイッチ enabled = "true" />は、JITに常にティア1をスキップさせる
  • またはRuntimeHelpers.PrepareMethodのようなものです。
    ホットパスの一部であるすべてのメソッドのコードによって呼び出されます(
    これを使用して、起動時にコードを事前にJITします)。 これには利点があります
    何を知っている必要がある開発者により大きな自由度を与える
    ホットパスはです。 このメソッドの追加のオーバーロードは問題ありません。

確かに、これから恩恵を受けるプロジェクトはほとんどありませんが、私はちょっと心配しています
JITはデフォルトで最適化をスキップし、私はそれを伝えることができません
代わりに、コードを大幅に最適化してもらいたいと思います。

デザインドキュメントに次のように書いていると思います。

マネージコードAPIからアクセスできる新しいビルドパイプラインステージを追加して、
自己変更コード。

これは非常に興味深いように聞こえます😁しかし、それが何をカバーしているかはよくわかりません

私はここで尋ねています。

また、関連する質問:2番目のJITパスはいつ開始されますか? いつ
メソッドはn回呼び出されますか? JITは
メソッドが実行されるはずだったスレッド? もしそうなら、それは導入します
メソッド呼び出しの前に遅延します。 より積極的に実装する場合
この遅延の最適化は、現在のJIT時間よりも長くなります。
問題になる可能性があります。


あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/dotnet/coreclr/issues/4331#issuecomment-312130920
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AGGWB2WbZ2qVBjRIQWS86MStTSa1ODfoks5sJCzOgaJpZM4IHWs8

@ ltrzesniewski-フィードバックありがとうございます! 確かに、段階的なコンパイルが大多数のプロジェクトに役立つことを願っていますが、トレードオフはすべてのプロジェクトにとって理想的ではないかもしれません。 階層型ジッティングを無効にするために環境変数をそのままにしておくと推測してきました。その場合、現在のランタイム動作をより高品質の(ただし生成が遅い)前もって維持します。 環境変数を設定することは、アプリにとって合理的なことですか? 他のオプションも可能です。これは、使用できる最も単純な構成オプションの1つであるため、環境変数に引き寄せられます。

また、関連する質問:2番目のJITパスはいつ開始されますか?

これは、時間の経過とともに進化する可能性が非常に高いポリシーです。 現在のプロトタイプの実装では、「メソッドが30回以上呼び出されていますか」という単純なポリシーが使用されています。
https://github.com/dotnet/coreclr/blob/master/src/vm/tieredcompilation.cpp#L89
https://github.com/dotnet/coreclr/blob/master/src/vm/tieredcompilation.cpp#L122

便利なことに、この非常に単純なポリシーは、たとえそれが単なる推測であっても、私のマシンでの優れたパフォーマンスの向上を示唆しています。 より良いポリシーを作成するには、実際の使用状況のフィードバックを取得する必要があります。そのフィードバックを取得するには、コアメカニズムがさまざまなシナリオで適度に堅牢である必要があります。 したがって、私の計画は、最初に堅牢性/互換性を改善してから、ポリシーを調整するためにさらに調査を行うことです。

@ DemiMarie-現在、ポリシーの一部としてループの反復を追跡するものはありませんが、将来の興味深い見通しです。

プロファイリング、投機的最適化、および
最適化解除? JVMはこれらすべてを実行します。

2017年6月29日午後8時58分、「NoahFalk」 [email protected]は次のように書いています。

@ltrzesniewskihttps ://github.com/ltrzesniewski-ありがとうございます
フィードバック! 確かに、階層型コンパイルが広大な人々に役立つことを願っています
プロジェクトの大部分ですが、トレードオフはすべてのプロジェクトにとって理想的ではない場合があります。
私は、環境変数をそのままにしておくと推測してきました。
階層型ジッティングを無効にします。この場合、実行時の動作を維持します。
今では、より高品質(ただし生成が遅い)で前もってジッティングしています。 は
アプリが実行するのに合理的な環境変数を設定しますか?
他のオプションも可能です、私はただ環境に引き寄せられます
これは、使用できる最も単純な構成オプションの1つであるためです。

また、関連する質問:2番目のJITパスはいつ開始されますか?

これは、時間の経過とともに進化する可能性が非常に高いポリシーです。 現在
プロトタイプの実装では、単純なポリシーを使用しています。
> = 30回呼び出されます」
https://github.com/dotnet/coreclr/blob/master/src/vm/
tieredcompilation.cpp#L89
https://github.com/dotnet/coreclr/blob/master/src/vm/
tieredcompilation.cpp#L122

便利なことに、この非常に単純なポリシーは、
たとえそれが単なる推測であっても、私のマシン。 より良いポリシーを作成するために
実際の使用状況のフィードバックを取得し、そのフィードバックを取得する必要があります
コアメカニズムは、さまざまな分野で適度に堅牢である必要があります
シナリオ。 ですから、私の計画は、最初に堅牢性/互換性を改善してから、
ポリシーを調整するためのさらなる調査。

@DemiMariehttps ://github.com/demimarie-私たちはそれを何も持っていません
現在、ポリシーの一部としてループの反復を追跡していますが、興味深い
将来の見通し。


あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/dotnet/coreclr/issues/4331#issuecomment-312146470
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AGGWB5m2qCnOKJsaXFCFigI3J6Ql8PMQks5sJEgZgaJpZM4IHWs8

@noahfalk環境変数は、アプリケーションごとにこのアプリケーションを制御できるソリューションではありません。 サーバー/サービスアプリの場合、通常、アプリケーションの起動にかかる時間は気にしません(パフォーマンスを犠牲にすることはありません)。 データベースエンジンを開発することは、私が直接あなたに言うことができます。私たちは、最初から、そして潜在的な新しいクライアントによって行われる例外的なパスやベンチマークでさえ、できるだけ速く動作する必要があります。

一方、一般的な環境では、稼働時間は一度に数週間で測定できるため、30秒でもかまいません。 私たちが気にしているのは、ユーザーに一般的なスイッチを発行するように強制すること(オールオアナッシング)、またはユーザーにそれを気にする必要があること(構成ファイルからデフォルトで設定されるなど)は10ステップ後退することです。

誤解しないでください。階層型JITは、JITレベルでの最適化に必要なコードパスと同じくらいの時間がかかるため、高性能のパスを開くので、私はこれ以上楽しみにしています。 私はずっと前にJITエンジニアの何人かとの非公式の話し合いで自分自身を提案しました、そしてあなたはすでにそれをレーダーに載せていました。 しかし、(システム全体ではなく)アプリケーション全体の動作をカスタマイズする方法は、(少なくとも私たちにとっては)この特定の機能の重要な品質指標です。

編集:いくつかのスタイルの問題。

@ redknightlois-フォローアップしてくれてありがとう

環境変数は、アプリケーションごとにこのアプリケーションを制御できるソリューションではありません。

この部分で少し混乱しています...少なくとも私が知っていたプラットフォームでは、環境変数はシステムごとの粒度ではなくプロセスごとの粒度を持っています。 たとえば、今日、私が実行する1つのアプリケーションでテストするために、階層型コンパイルをオンにするには、次のようにします。

set COMPLUS_EXPERIMENTAL_TieredCompilation=1
MyApp.exe
set COMPLUS_EXPERIMENTAL_TieredCompilation=0

私たちが気にしているのは、[私たちは]ユーザーにそれを気にするように強制することです

アプリを実行している人ではなく、アプリの開発者が指定できる構成設定が必要だと思いますか? env varの1つの可能性は、ユーザーがcoreclrアプリを起動する簡単なラッパー(バッチスクリプトなど)を起動するようにすることですが、少しエレガントではないようです。 私は代替案を受け入れており、envvarに設定されていません。 期待を設定するためだけに、これは私が近い将来積極的な設計努力を費やす領域ではありませんが、適切な構成を持つことが重要であることに同意します。

また、注意が必要です。階層型コンパイルパスを適切な方法で続行すると、階層型コンパイルを有効にすることが最速のスタートアップであるだけでなく、現在の定常状態のパフォーマンスを上回っているという点に到達することは容易に想像できます。 今のところ、startup perfが私のターゲットですが、それを使ってできることの限界ではありません:)

プロファイリング、投機的最適化、および
最適化解除?

@ DemiMarie-彼らは確かに会話の中で登場しており、階層化されたコンパイルがこれらの可能性を開くことに多くの人々が興奮していると思います。 私自身のために言えば、私は自分の目標を高く設定する前に、基本的な階層型コンパイル機能を提供することに集中し続けようとしています。 私たちのコミュニティの他の人々は、おそらくすでに他のアプリケーションで私をフロントランニングしています。

@noahfalkはい、エレガントでないということは、それを実行する通常のプロセスがエラーを起こしやすくなる可能性があることを意味し、それが本質的に問題になります(誰も混乱しないことを完全に確認する唯一の方法は、システム全体で実行することです)。 それが機能することがわかっている別の方法は、 app.configのエントリでサーバーGCを使用する場合に構成できるのと同じ方法で、階層化されたコンパイルでも同じことを実行できることです(少なくとも階層化は、定常状態のパフォーマンスを一貫して上回ることができます)。 JITであるため、 assembly.configを使用してアセンブリごとにそれを行うこともでき、他のノブもそのように選択できる場合は、現在存在しない程度の機能を提供します。

多くの場合、環境変数はユーザーごとまたはシステムごとに設定されます。これは、ランタイムの複数のバージョンにわたって、そのようなすべてのプロセスに影響を与える可能性のある悪影響を及ぼします。 アプリごとの構成ファイルは、はるかに優れたソリューションのようです(ユーザーごと/システムごとも利用できる場合でも)-app.configで設定できるデスクトップ構成値のようなものですが、envvarsまたはregistryも使用します。

アプリケーションごとの最も一般的なパスを実装すると思います。 システム全体の設定も役立つかもしれませんが、機能を実装する前にそれについて考える必要はないと思います。

いくつかのアイデアはありますが、最適化のために第2層のjitが何をすべきかについては詳細に解明されていないことに注意してください。 それは、今日のjitが行うことを実行するだけかもしれませんが、それ以上のことを実行する可能性が非常に高いです。

それで、いくつかの潜在的な合併症を指摘させてください。

第2層のjitは、第1層のjitによって作成されたコードの動作について行われた観察に加えて、それ自体をブートストラップする可能性があります。 したがって、第1層のjitをバイパスして、第2層のjitを直接要求することは、階層化を実行させるだけでは、まったく機能しないか、うまく機能しない可能性があります。 おそらく、「階層化バイパス」オプションは、実装されていても、第2層のjitが生成できるコードではなく、今日のデフォルトでjitが生成するコードのようなコードを提供することになります。

第2層のjitは、多数のメソッドのセットで実行すると比較的遅いjit時間が発生するように調整できます(比較的少数のメソッドが第2層のjitでjitされることになると予想されるため、第2層のjitは、より徹底的な最適化を行います)。 ここでは、適切なトレードオフはまだわかりません。

そうは言っても...

「アグレッシブな最適化」メソッド属性は理にかなっていると思います。jitに特定のメソッドに対して動作する可能性のある第2層のjitのように動作するように要求し、プリジット中にこれらのメソッドをスキップする可能性があります(prejittedコードはjittedコードよりも実行が遅いため、特にR2Rの場合)。 ただし、この概念をアセンブリ全体またはアプリケーション内のすべてのアセンブリに適用することは、魅力的ではないようです。

ネイティブコンパイラで発生することを適切な例えとして考えると、パフォーマンスとコンパイル時間/コードサイズのトレードオフは、最適化レベルが高くなるとかなり悪くなる可能性があります。たとえば、コンパイルが10倍長くなると、パフォーマンスが全体で1〜2%向上します。 パズルの鍵は、どの方法が重要かを知ることです。それを行う唯一の方法は、プログラマーが知っているか、システムがそれを自分で理解することです。

@AndyAyersMSあなたはそこに釘を打ったと思います。 「積極的な最適化」属性を扱うJITは、おそらく、第1層のJITがフィードバックを提供する時間がなくても、JITが分離でより良いコードを生成するのに十分な情報を得ることができないという問題のほとんどを解決します。

@redknightlois属性は、より多くの階層化が必要な場合は機能しません。-T3JIT、T4 JIT、... 2つのレベルでは不十分かどうかはわかりませんが、少なくともこの可能性を考慮する必要があります。

MPGOに似たものを使用して、第2層のjittedコードで実行を開始できると便利です。 最初の層を完全にバイパスするのではなく、早送りします。

@ AndyAyersMS 、AzulがLLVMを使用してJVMにマネージドJITを実装したことで、LLVMをCLRに統合するのが簡単になったという事実はありますか? どうやら、変更はプロセスのLLVMにアップストリームでプッシュされました。

ちょうどfyi、私は地面から階層化されたジッティングを取得するために行う必要があるいくつかの特定の作業のためにいくつかの作業項目を作成しました(#12609、dotnet / coreclr#12610、dotnet / coreclr#12611、dotnet / coreclr#12612、dotnet / coreclr #12617)。 あなたの興味がそれらの1つに直接関係しているなら、あなたのコメントをそれらに自由に追加してください。 他のトピックについては、ここで議論が続くと思います。または、特定のサブトピックを単独で分割する価値がある場合は、誰でも特定のサブトピックの問題を作成できます。

@MendelMonteiro Jitting時にMPGOスタイルのフィードバックデータを利用できるようにすることは確かにオプションです(現在、このデータはprejitting時にのみ読み取ることができます)。 インストルメントできるものにはさまざまな制限があるため、すべてのメソッドをこのように処理できるわけではありません。他にも確認する必要のある制限があります(たとえば、インラインのフィードバックデータはありません)。 MPGOデータは多くのユーザーにとって障壁であり、MPGOデータは、最初の層からブートストラップするときに得られるものと一致する場合と一致しない場合がありますが、このアイデアには確かにメリットがあります。

LLVMベースの上位層に関しては、明らかにLLILCである程度調査しており、当時はAzulの人々と頻繁に連絡を取り合っていたため、彼らが行っていたことの多くに精通しています。 LLVMは、正確なGCを使用した言語のコンパイルをより受け入れやすくします。

GCとEHの両方で、CLRに必要なLLVMサポートとJavaに必要なサポートには大きな違いがあり、オプティマイザーに課す必要のある制限もありました。 一例を挙げると、CLR GCは現在、オブジェクトの終わりを指すマネージポインターを許容できません。 Javaは、ベース/派生ペアのレポートメカニズムを介してこれを処理します。 この種のペアレポートのサポートをCLRに組み込むか、LLVMのオプティマイザーパスを制限して、これらの種類のポインターを作成しないようにする必要があります。 その上、LLILC jitは低速であり、最終的にどのようなコード品質が生成されるかはわかりませんでした。

したがって、LLILCがまだ存在していなかった潜在的な多層アプローチにどのように適合するかを理解することは、時期尚早であるように思われました(そしてまだそう思われます)。 今のところ、フレームワークに階層化して、2番目の層のjitにRyuJitを使用するという考え方です。 私たちがさらに学ぶにつれて、私たちは確かに高層のジットの余地があることに気付くかもしれません、あるいは少なくとも、そのようなことが意味をなす前に私たちが他に何をする必要があるかをよりよく理解するかもしれません。

@AndyAyersMSおそらく、制限を回避するよりも、LLVMに必要な変更を導入することができます。

マルチコアJITとそのプロファイル最適化はcoreclrで機能しますか?

@ benaadams-ええ、マルチコアJITは機能します。 デフォルトで有効になっているシナリオ(ある場合)を思い出せませんが、構成を介して有効にすることができます: https ://github.com/dotnet/coreclr/blob/master/src/inc/clrconfigvalues.h#

私はハーフトイコンパイラを作成しましたが、ほとんどの場合、ハードヒットの最適化は同じインフラストラクチャでかなり問題なく実行でき、上位層のオプティマイザではほとんど実行できないことに気付きました。

つまり、関数が何度もヒットした場合、パラメーターは次のようになります。

  • インライン命令数を増やす
  • より「高度な」レジスタアロケータ(LLVMのようなバックトラッキングカラーレータまたはフルカラーライザ)を使用する
  • 最適化のパスをさらに実行します。おそらく、地域の知識に特化したものもあります。 例:オブジェクトがメソッドで宣言されており、より大きなインライン関数の本体で割り当てられていない場合は、完全なオブジェクト割り当てをスタック割り当てに置き換えることができます。
  • CHAが不可能なほとんどのヒットオブジェクトにはPICを使用します。 たとえば、StringBuilderはオーバーライドされない可能性が非常に高く、StringBuilderでヒットされるたびにコードがマークされると、内部で呼び出されるすべてのメソッドを安全に非仮想化でき、SBのアクセスの前にタイプガードが設定されます。

それも非常に素晴らしいことですが、CompilerServicesがコードやメタデータを介してアクセスできるように公開される「高度なコンパイラ」を提供することは私の夢の目覚めかもしれません。どのクラスとメソッドを「より深くコンパイル」するかを事前にコンパイルします。 これはNGenではありませんが、非階層型コンパイラが必ずしも可能ではない(望ましい)場合は、少なくとも、この追加のパフォーマンスを必要とする重要な部分に、より重い最適化コードを使用できるようにする必要があります。 もちろん、プラットフォームが大幅な最適化を提供しない場合(たとえば、Mono)、API呼び出しは基本的にNO-OPになります。

@ noahfalk@ kouvelなどのハードワークのおかげで、現在、階層化のための強固な基盤が整っています。

この問題を閉じて、「階層型ジッティングを改善するにはどうすればよいか」という問題を開くことをお勧めします。 このトピックに興味のある人は、現在の階層化を試して、現在の状況を把握することをお勧めします。 良いか悪いかにかかわらず、実際の行動についてのフィードバックをお待ちしています。

現在の動作はどこかに記述されていますか? 私はこれを見つけただけですが、それは具体的な階層化ではなく、実装の詳細に関するものです。

収集したデータの一部を使用して、ある種の要約記事がまもなく利用可能になると思います。

2.1では、 COMPlus_TieredCompilation=1を設定することで階層化を有効にできます。 試してみたら、見つけたものを報告してください。

最近のPR(https://github.com/dotnet/coreclr/pull/17840、https://github.com/dotnet/sdk/pull/2201)では、ランタイム構成として階層型コンパイルを指定することもできます。 jsonプロパティまたはmsbuildプロジェクトプロパティ。 この機能を使用するには、環境変数がしばらく前から存在しているのに対し、ごく最近のビルドを使用する必要があります。

@jkotasで前に説明したように、TieredJITは起動時間を改善できます。 ネイティブ画像を使用すると機能しますか?
Tizen電話でいくつかのアプリの測定を行い、結果があります。

システムDLL |アプリDLL |階層化|時間、s
----------- | -------- | ------ | --------
R2R | R2R |いいえ| 2.68
R2R | R2R |はい| 2.61 (-3%)
R2R |いいえ|いいえ| 4.40
R2R |いいえ|はい| 3.63 (-17%)

FNVモードもチェックしますが、画像がない場合は問題なく動作するようです。

cc @gbalykov @ nkaretnikov2

参考までに、階層型コンパイルが.NET Coreのデフォルトになりました: https ://github.com/dotnet/coreclr/pull/19525

@ alpencolt 、R2RなどのAOTコンパイルを使用すると、起動時間の改善が少なくなる場合があります。 起動時間の改善は、現在、最適化を減らしてより迅速にJITを実行することで実現されており、AOTコンパイルを使用すると、JITの処理が少なくなります。 一部のジェネリック、ILスタブ、その他の動的メソッドなど、一部のメソッドは事前生成されていません。 一部のジェネリックは、AOTコンパイルを使用している場合でも、起動時に階層化の恩恵を受ける可能性があります。

@kouvelのコミットにより、タイトルの質問を達成したと思うので、この問題を閉じます。D人々は、要求された改善など、より具体的なトピックについて議論を続けたり、新しい問題を開いたりすることができます。質問、または特定の調査。 もちろん、時期尚早に閉鎖されていると思われる方はお知らせください。

@kouvelクローズされた問題についてコメントして申し訳ありません。 crossgenなどのAOTコンパイルを使用する場合、アプリケーションはホットスポットコードパスの第2層コンパイルの恩恵を受けますか?

@ daxian-dbwはい非常にそうです。 実行時に、Jitは(dll間で)クロスアセンブリインライン化を実行できます。 実行時定数に基づくブランチの削除( readonly static ); 等

@benaadamsそして、うまく設計されたAOTコンパイラはできませんでしたか?

https://blogs.msdn.microsoft.com/dotnet/2018/08/02/tiered-compilation-preview-in-net-core-2-1/でこれに関するいくつかの情報を見つけました:

プリコンパイルされたイメージには、バージョン管理の制約とCPU命令の制約があり、一部のタイプの最適化が禁止されています。 頻繁に呼び出されるこれらのイメージのメソッドの場合、階層化コンパイルは、プリコンパイルされたバージョンを置き換えるバックグラウンドスレッドで最適化されたコードを作成するようにJITに要求します。

ええ、それは「うまく設計されていないAOT」の例です。 😛

プリコンパイルされたイメージには、バージョン管理の制約とCPU命令の制約があり、一部のタイプの最適化が禁止されています。

例の1つは、ハードウェア組み込みを使用するメソッドです。 AOTコンパイラ(crossgen)は、x86 / x64のcodegenターゲットとしてSSE2を想定しているため、ハードウェア組み込みを使用するすべてのメソッドは、crossgenによって拒否され、基盤となるハードウェア情報を知っているJITによってコンパイルされます。

そして、うまく設計されたAOTコンパイラはできませんでしたか?

AOTコンパイラには、リンク時の最適化(クロスアセンブリインライン化の場合)とプロファイルガイドによる最適化(ランタイム定数の場合)が必要です。 一方、AOTコンパイラは、SIMDコードのビルド時に「最終的な」ハードウェア情報(gcc / clangの-mavx2など)を必要とします。

例の1つは、ハードウェア組み込みを使用するメソッドです。 AOTコンパイラ(crossgen)は、x86 / x64のcodegenターゲットとしてSSE2を想定しているため、ハードウェア組み込みを使用するすべてのメソッドは、crossgenによって拒否され、基盤となるハードウェア情報を知っているJITによってコンパイルされます。

待って、何? 私はここを完全にはフォローしていません。 AOTコンパイラが組み込みを拒否するのはなぜですか?

そして、うまく設計されたAOTコンパイラはできませんでしたか?

AOTコンパイラには、リンク時の最適化(クロスアセンブリインライン化の場合)とプロファイルガイドによる最適化(ランタイム定数の場合)が必要です。 一方、AOTコンパイラは、SIMDコードのビルド時に「最終的な」ハードウェア情報(gcc / clangの-mavx2など)を必要とします。

はい、私が言ったように、「うまく設計されたAOTコンパイラ」です。 😁

@masonwheeler別のシナリオ; crossgenはAoTであり、Jitと連携し、アプリケーションの完全な再コンパイルと再配布を必要とせずにdllのサービス/パッチを適用できます。 Tier1よりも起動が速く、Tier0よりも優れたコード生成を提供します。 ただし、プラットフォームは中立ではありません。

Tier0、crossgen、Tier1はすべて、 coreclrのまとまりのあるモデルとして連携して機能します。

インライン(非Jit)でクロスアセンブリするには、静的にリンクされた単一ファイルの実行可能ファイルをコンパイルする必要があり、使用するライブラリにパッチを適用するためにアプリケーションを完全に再コンパイルして再配布する必要があります。また、特定のプラットフォーム(どのバージョンの使用するSSE、Avxなど。最も一般的でないバージョンまたはすべての製品バージョン?)。

corertは、このスタイルのアプリケーションをAoTします。

でも; Jitが実行できる特定のタイプのブランチ除去を実行すると、代替パス用に大量の追加のasm生成が必要になります。 正しいツリーの実行時パッチ

たとえば、次のようなメソッドを使用するコード(Tier1 Jitがすべてのifを削除する場合)

readonly static _numProcs = Environment.ProcessorCount;

public void DoThing()
{
    if (_numProcs == 1) 
    {
       // Single proc path
    }
    else if (_numProcs == 2) 
    {
       // Two proc path
    }
    else
    {
       // Multi proc path
    }
}

@benaadams

インライン(非Jit)でクロスアセンブリするには、静的にリンクされた単一ファイルの実行可能ファイルをコンパイルする必要があり、使用するライブラリにパッチを適用するためにアプリケーションを完全に再コンパイルして再配布する必要があります。また、特定のプラットフォーム(どのバージョンの使用するSSE、Avxなど。最も一般的でないバージョンまたはすべての製品バージョン?)。

アプリケーションを完全に再配布する必要はありません。 AndroidのARTコンパイルシステムを見てください。アプリケーションをマネージコード(Javaの場合はJavaですが、同じ原則が適用されます)として配布し、ローカルシステム上にあるコンパイラーは、マネージコードを超最適化されたネイティブ実行可能ファイルにコンパイルします。

小さなライブラリを変更しても、すべてのマネージコードが残っているので、パッチを適用したものだけを再配布する必要はありません。その後、AOTを再実行して新しい実行可能ファイルを作成できます。 (明らかに、これはAndroidのAPKアプリ配布モデルのためにAndroidのアナロジーが崩壊するところですが、それはデスクトップ/サーバー開発には当てはまりません。)

そして、ローカルシステム上にあるコンパイラは、AOTがマネージコードをコンパイルします...

これは、完全なフレームワークが使用していた以前のNGenモデルです。 フレームワークコードをアプリケーションコードにインライン化する単一のアセンブリを作成したとは思いませんか? 2つのアプローチの違いは、.NET Core2.1で実行されるBing.comで強調されました。 ブログ投稿

ReadyToRun画像

マネージドアプリケーションは、メソッドを最初にマシンコードにJITコンパイルする必要があるため、起動パフォーマンスが低下することがよくあります。 .NET Frameworkには、プリコンパイルテクノロジであるNGENがあります。 ただし、NGENでは、コードが実行されるマシンでプリコンパイル手順を実行する必要があります。 Bingの場合、これは数千台のマシンでNGENを実行することを意味します。 これと積極的な展開サイクルを組み合わせると、アプリケーションがWebサービングマシンでプリコンパイルされるため、サービング容量が大幅に削減されます。 さらに、NGENを実行するには管理者権限が必要です。管理者権限は、データセンターの設定では利用できないか、綿密に調査されていることがよくあります。 .NET Coreでは、crossgenツールを使用すると、ビルドラボなどでコードをデプロイ前の手順としてプリコンパイルでき、本番環境にデプロイされたイメージはすぐに実行できます。

@masonwheeler AOTは、.Netプロセスの動的な性質により、完全な.Netで逆風に直面しています。 たとえば、.Netのメソッド本体はプロファイラーを介していつでも変更でき、クラスはリフレクションを介してロードまたは作成でき、相互運用などの必要に応じてランタイムによって新しいコードを作成できます。したがって、手続き間の分析情報はせいぜい反映されます。実行中のプロセスの一時的な状態。 .Netでの手続き間の分析または最適化(インライン化を含む)は、実行時に元に戻すことができる必要があります。

AOTは、AOTの時間と実行時間の間で変更される可能性のあるもののセットが小さく、そのような変更の影響がローカライズされている場合に最適に機能します。選択肢の数)。

.Netプロセスの動的な性質に対処または制限するためのメカニズムを組み込むことができる場合、純粋なAOTは非常にうまく機能します。たとえば、.Net Nativeは反射と相互運用の影響を考慮し、アセンブリの読み込み、反射の放出、および(私は推測します)プロファイルが添付されます。 しかし、それは単純ではありません。

crossgenの範囲を複数のアセンブリに拡張して、すべてのコアフレームワーク(またはすべてのasp.netアセンブリ)をバンドルとしてAOTコンパイルできるようにするための作業が進行中です。 しかし、状況が変化したときにcodegenをやり直すためのフォールバックとしてjitがあるため、これは実行可能です。

@AndyAyersMSここで説明している理由から、

可能な場合は静的、必要な場合は動的。

System.Linq.Expressionsから
public TDelegate Compile(bool PreferredInterpretation);

PreferredInterpretationがtrueの場合、階層化されたコンパイルは引き続き機能しますか?

このページは役に立ちましたか?
0 / 5 - 0 評価