プロファイルガイド付き最適化

プロファイルガイド付き最適化とは? #

Just-in-Time(JIT)コンパイラは、Ahead-of-Time(AOT)コンパイラよりも、アプリケーションの実行時動作を分析できるという利点があります。たとえば、HotSpotは、if文の各分岐が実行された回数を追跡します。「プロファイル」と呼ばれるこの情報は、ティア2 JITコンパイラ(Graalなど)に渡されます。ティア2 JITコンパイラは、if文が同じように動作し続けると仮定し、プロファイルからの情報を使用してその文を最適化します。

AOTコンパイラは通常、プロファイリング情報を持たず、コードの静的なビューに限定されています。つまり、ヒューリスティックを除けば、AOTコンパイラは、すべてのif文の各分岐が実行時に発生する可能性が等しく、各メソッドは他のメソッドと同じように呼び出される可能性があり、各ループは同じ回数繰り返されると見なします。これはAOTコンパイラを不利な立場に置きます。プロファイリング情報がないと、JITコンパイラと同じ品質のマシンコードを生成することは困難です。

プロファイルガイド付き最適化(PGO)は、プロファイリング情報をAOTコンパイラに提供して、パフォーマンスとサイズに関して出力の品質を向上させる手法です。

注:PGOはGraalVM Community Editionでは使用できません。

プロファイルとは? #

プロファイルは、アプリケーションの実行中に特定のイベントが発生した回数の要約ログです。イベントは、コンパイラがより良い決定を行うために役立つ情報に基づいて選択されます。例としては、

  • このメソッドは何度呼び出されましたか?
  • このif文はtrue分岐を何回実行しましたか?false分岐を何回実行しましたか?
  • このメソッドはオブジェクトを何回割り当てましたか?
  • 特定のinstanceofチェックにString値が何回渡されましたか?

アプリケーションのプロファイルを取得するにはどうすればよいですか? #

JITコンパイラを搭載したJVMでアプリケーションを実行する場合、アプリケーションのプロファイリングはランタイム環境によって処理され、開発者による追加の手順は必要ありません。ただし、プロファイルの作成により、プロファイルされているアプリケーションのパフォーマンスに実行時間とメモリ使用量のオーバーヘッドが追加されます。これにより、ウォームアップの問題が発生します。アプリケーションは、アプリケーションコードがプロファイルされ、JITコンパイルされるのに十分な時間が経過した後にのみ、予測可能なピークパフォーマンスに達します。長時間実行されるアプリケーションの場合、このオーバーヘッドは通常、後でパフォーマンスの向上をもたらすため、それ自体を償却します。一方、短命なアプリケーションや、できるだけ早く予測可能なパフォーマンスで開始する必要があるアプリケーションでは、これは逆効果です。

AOTコンパイルされたアプリケーションのプロファイルを収集する方が複雑で、開発者による追加の手順が必要になりますが、最終的なアプリケーションにはオーバーヘッドは発生しません。プロファイルは、アプリケーションの実行中に観察することによって収集する必要があります。これは一般的に、アプリケーションバイナリに計測コードを挿入する特別なモードでアプリケーションをコンパイルすることによって実現されます。計測コードは、プロファイルに関心のあるイベントのカウントを増分します。計測コードを含むバイナリは計測バイナリと呼ばれ、これらのカウンタを追加するプロセスは計測と呼ばれます。

当然のことながら、計測コードのオーバーヘッドのために、アプリケーションの計測バイナリはデフォルトのバイナリほど高性能ではありません。そのため、本番環境で実行することはお勧めしません。しかし、計測バイナリで合成的な代表的なワークロードを実行すると、アプリケーションの動作の代表的なプロファイルが得られます。最適化されたアプリケーションをビルドする場合、AOTコンパイラは、アプリケーションの静的なビューと動的なプロファイルの両方を使用します。したがって、最適化されたアプリケーションは、デフォルトのAOTコンパイルされたアプリケーションよりもパフォーマンスが向上します。

プロファイルはどのように最適化を「ガイド」しますか? #

コンパイル中、コンパイラは最適化に関する決定を行う必要があります。たとえば、次のメソッドでは、関数インライン化最適化は、どの呼び出しサイトをインライン化し、どれをインライン化しないかを決定する必要があります。

private int run(String[] args) {
    if (args.length < 3) {
        return handleNotEnoughArguments(args);
    } else {
        return doActualWork(args);
    }
}

説明のために、インライン化最適化は生成できるコードの量に制限があり、したがって呼び出しの1つだけをインライン化できると想像してください。コンパイルされているコードの静的なビューのみを見ると、doActualWork()handleNotEnoughArguments()の両方の呼び出しは、ほとんど区別できません。ヒューリスティックがない場合、フェーズはどちらがインライン化するためのより良い選択肢かを推測する必要があります。ただし、間違った選択を行うと、効率の低いコードにつながる可能性があります。run()が実行時に正しい数の引数で最も頻繁に呼び出されると仮定すると、handleNotEnoughArgumentsをインライン化すると、doActualWork()への呼び出しはほとんどの場合行う必要があるため、コンパイルユニットのコードサイズが増加してもパフォーマンス上のメリットはありません。

アプリケーションの実行時プロファイルを使用すると、コンパイラは呼び出しを区別するためのデータを得ることができます。たとえば、実行時プロファイルがif条件を100回false、3回trueとして記録した場合、doActualWork()をインライン化する必要があります。これがPGOの本質です。プロファイルの情報を使用して、特定の決定を行う際にコンパイラにデータに基づいた根拠を与えることです。実際の決定とプロファイルが記録する実際のイベントはフェーズごとに異なりますが、上記の例は一般的なアイデアを示しています。

PGOは、アプリケーションの計測バイナリで代表的なワークロードを実行することを期待していることに注意してください。非生産的なプロファイル(アプリケーションの実際の実行時動作とは正反対の動作を記録するプロファイル)を提供することは、非生産的です。上記の例では、実際のアプリケーションでは行われないのに、引数が少なすぎるワークロードでrun()メソッドを呼び出す計測バイナリを実行することになります。これにより、インライン化フェーズはhandleNotEnoughArgumentsをインライン化することを選択し、最適化されたバイナリのパフォーマンスが低下します。

したがって、目標は、本番ワークロードとできるだけ一致するワークロードに関するプロファイルを収集することです。これに対するゴールドスタンダードは、本番環境で実行することを期待しているものとまったく同じワークロードを計測バイナリで実行することです。

より詳細な使用方法の概要については、プロファイルガイド付き最適化の基本的な使用方法ドキュメントを参照してください。

さらに読む #

お問い合わせ