C ++などの静的にコンパイルされた言語の実行時パフォーマンスを改善するためにJITトリックを使用している人はいますか?実行時に行われた監視に基づくホットスポット分析と分岐予測は、コードのパフォーマンスを向上させる可能性があるようですが、実行時にそのような監視と変更の実装が仮想マシンでのみ可能であるという基本的な戦略的理由があるかもしれません。動的言語愛好家が統計の収集とコードの再配置について話しているのを聞きながら、C ++コンパイラの作成者が「C++で記述されたプログラムでも同じことができる」とつぶやくのを耳にしたことをはっきりと覚えていますが、このメモリをサポートする証拠をWebで検索することはできません。
7 に答える
プロファイルに基づく最適化は、ランタイム最適化とは異なります。最適化は、プロファイリング情報に基づいて引き続きオフラインで行われますが、バイナリが出荷されると進行中の最適化は行われないため、プロファイルに基づく最適化フェーズの使用パターンが実際の使用を正確に反映していない場合、結果は次のようになります。不完全であり、プログラムはさまざまな使用パターンにも適応しません。
HP の Dynamoに関する情報を探すのは興味深いかもしれませんが、そのシステムはネイティブ バイナリ -> ネイティブ バイナリ変換に焦点を当てていましたが、C++ はほとんどネイティブ コードにコンパイルされているため、まさにあなたが探しているものだと思います。
また、コンパイラ フレームワークであり、JIT コンパイルとランタイム最適化をサポートする中間表現であるLLVMを確認することもできますが、C++ をコンパイルして + ランタイムを実行できる LLVM ベースのランタイムが実際に存在するかどうかはわかりません。まだ最適化します。
ここ数年、私はそのような最適化をかなり行ってきました。私が実装したのは、グラフィック レンダリング API 用でした。API は汎用関数として数千の異なる描画モードを定義していたため、速度が低下していました。
ドメイン固有言語用の独自の小さな Jit コンパイラを作成することになりました (asm に非常に近いですが、いくつかの高レベルの制御構造とローカル変数がスローされます)。
私が得たパフォーマンスの向上は 10 倍から 60 倍 (コンパイルされたコードの複雑さによって異なります) であったため、余分な作業は大きな成果を上げました。
PC では、自分の jit コンパイラを書き始めるのではなく、jit コンパイルに LIBJIT または LLVM を使用していました。私の場合、LIBJIT/LLVM でサポートされていない非主流の組み込みプロセッサで作業していたため、これは不可能でした。そのため、独自のプロセッサを発明する必要がありました。
答えはもっとありそうです:利点が目立たない可能性が高いので、C++に対してPGO以上のことをした人は誰もいません。
詳細を説明します。JITエンジン/ランタイムには、開発者の観点からは長所と短所の両方があります。実行時の情報は多くなりますが、分析する時間はほとんどありません。いくつかの最適化は非常に費用がかかり、開始時間に大きな影響を与えずに、ループ展開、自動ベクトル化(ほとんどの場合、ループ展開にも基づいています)、命令選択(SSE4.1を使用するため)のようなものはほとんど見られません。 SSE4.1を使用するCPU)と命令スケジューリングおよび並べ替え(より優れたスーパースカラーCPUを使用するため)を組み合わせたもの。この種の最適化は、Cのようなコード(C ++からアクセス可能)とうまく組み合わされています。
高度なコンパイル(私が知る限り)を行うための単一の本格的なコンパイラアーキテクチャは、Java Hotspotコンパイルと、階層型コンパイルを使用した同様の原則を持つアーキテクチャです(Java Azulのシステム、今日のJaegerMonkey JSエンジンで人気があります)。
ただし、実行時の最大の最適化の1つは次のとおりです。
多態的なインラインキャッシング(つまり、最初のループをいくつかの型で実行すると、2回目は、ループのコードは前のループからの特殊な型になり、JITはガードを配置し、デフォルトのブランチとしてインライン化されますタイプ、およびそれに基づいて、 SSAフォームエンジンベースを使用するこの特殊なフォームから、定数畳み込み/伝播、インライン化、デッドコード除去の最適化が適用され、JITがどの程度「高度」であるかに応じて、改善またはあまり改善されていないCPUレジスタ割り当て。)お気づきかもしれませんが、JIT(ホットスポット)は主に分岐コードを改善し、ランタイム情報を使用するとC ++コードよりも良くなりますが、静的コンパイラーは、分析、命令の並べ替え、単純なループを行う時間があるため、少し良くなる可能性がありますパフォーマンス。また、通常、C ++コードでは、高速である必要がある領域はOOPではない傾向があるため、JIT最適化の情報はそのような驚くべき改善をもたらしません。
JITのもう1つの利点は、JITがアセンブリ間で機能することです。したがって、インライン化を行う場合は、より多くの情報が得られます。
詳細に説明します。基本クラスAがあり、その実装が1つだけ、つまり別のパッケージ/アセンブリ/ gem/etcにBがあるとします。動的にロードされます。
JITは、BがAの唯一の実装であり、内部表現のどこでもA呼び出しをBコードに置き換えることができ、メソッド呼び出しはディスパッチ(vtableを参照)を実行せず、直接呼び出しになります。これらの直接呼び出しもインライン化される場合があります。たとえば、このBにはメソッドがあります。getLength()
これは2を返し、のすべての呼び出しgetLength()
を定数2に減らすことができます。最後に、C++コードは別のdllからのBの仮想呼び出しをスキップできなくなります。
C ++の一部の実装は、より多くの.cppファイルの最適化をサポートしていません(今日でも、これを可能にする最近のバージョンのGCCには-ltoフラグがあります)。ただし、速度を懸念するC ++開発者の場合は、すべての機密クラスを同じ静的ライブラリまたは同じファイルに配置する可能性が高いため、コンパイラはそれを適切にインライン化して、JITが設計上持っている追加情報を作成できます。 、開発者自身が提供するため、パフォーマンスが低下することはありません。
Visual Studioには、コードの最適化に使用できるランタイムプロファイリングを実行するためのオプションがあります。
「プロファイルガイド付き最適化」
Microsoft Visual Studioは、これを「プロファイルガイド付き最適化」と呼んでいます。詳細については、 MSDNをご覧ください。基本的に、ホットスポットやその他のパフォーマンス特性を記録するためにプロファイラーを接続してプログラムを何度も実行し、プロファイラーの出力をコンパイラーにフィードして適切な最適化を行うことができます。
私は、 LLVMがこれのいくつかを行おうとしていると信じています。プログラムの存続期間全体 (コンパイル時、リンク時、および実行時) にわたって最適化を試みます。
合理的な質問-しかし、疑わしい前提があります。
Nilsの回答のように、「最適化」は「低レベルの最適化」を意味する場合があります。これは、それ自体が優れたテーマです。
ただし、これは「ホットスポット」の概念に基づいており、一般的に与えられている関連性にはほど遠いものです。
定義:ホットスポットは、プロセスのプログラムカウンターがその時間の大部分を費やす、コードの小さな領域です。
タイトな内部ループが多くの時間を占めるなどのホットスポットがある場合、それが制御するコードにある(つまり、サードパーティのライブラリにない)場合は、低レベルで最適化を試みる価値があります。
ここで、内部ループに関数、任意の関数への呼び出しが含まれていると仮定します。これで、関数内にある可能性が高いため、プログラムカウンターがそこに見つかる可能性は低くなります。したがって、コードは無駄になるかもしれませんが、もはやホットスポットではありません。
ソフトウェアを遅くする一般的な方法はたくさんありますが、そのうちの1つがホットスポットです。しかし、私の経験では、それはほとんどのプログラマーが知っている唯一のものであり、低レベルの最適化が適用される唯一のものです。