iprofファイル形式
ノート: このドキュメントでは、読者がGraalVM Profile-Guided Optimizationに精通していることを前提としています。
プロファイルに基づく最適化(PGO)を使用して最適化されたネイティブ・イメージを構築するには、インストゥルメントされたイメージでワークロードを実行することで収集されたプロファイリング・データをnative-image
ツールに提供する必要があります。このプロファイリング情報は、拡張子が.iprofのファイルにJSONオブジェクトとして格納されます。このドキュメントでは、iprofファイル形式の構造とセマンティクスについて概説します。
構造
iprofファイルに使用されるJSON形式の完全なスキーマは、iprof-v1.0.0.schema.jsonドキュメントにあります。このJSONスキーマは、iprofファイル形式を完全に定義し、任意のiprofファイルの構造を検証するために使用できます。
最小限の有効なiprofファイルは、types
、methods
およびversion
の3つのフィールドを含むJSONオブジェクトで構成されます。次に、現在のバージョン(1.0.0
)の最小限の有効なiprofファイルを示します。
{
"version": "1.0.0",
"types": [],
"methods": []
}
これらのフィールドに加えて、iprofファイルには、様々な実行時プロファイルに関する情報を提供するその他のフィールドをオプションで含めることができます。次に、各フィールドの実際の内容が...
に置き換えられた、完全移入されたiprofファイル(バージョン1.0.0
)の例を示します。
{
"version": "1.0.0",
"types": [...],
"methods": [...],
"monitorProfiles": [...],
"virtualInvokeProfiles": [...],
"callCountProfiles": [...],
"conditionalProfiles": [...],
"samplingProfiles": [...]
}
このドキュメントの以降の項では、具体的な例を示し、iprofファイルの各フィールドについて詳しく説明します。
具体的な例
最初の10個のフィボナッチ数を計算して出力する次のJavaプログラムを考えてみます。
import java.io.*;
public class Fib {
private int n;
Fib(int n) {
this.n = n;
}
synchronized void fibonacci() {
int num1 = 0, num2 = 1;
for (int i = 0; i < n; i++) {
try {
Thread.sleep(10);
} catch (Exception e) {
// ignored
}
// Print the number
System.out.print(num1 + " ");
// Swap
int num3 = num2 + num1;
num1 = num2;
num2 = num3;
}
}
public static void main(String args[])
{
new Fib(10).fibonacci();
}
}
このアプリケーションは、iprofファイルの構造とセマンティクスを説明する例として使用されます。このアプリケーションからiprofファイルを生成するには、Fib.javaとして保存し、次のコマンドを1つずつ実行します:
javac Fib.java
native-image --pgo-instrument -cp . Fib
./fib
fib
の終了後、作業ディレクトリにdefault.iprof
ファイルが存在するはずです。
ノート: このドキュメント全体に表示される正確な値は、実際の実行環境では異なる可能性があるため、ドキュメント全体で示された内容を確認する場合は、値のセマンティクスを理解する必要があります。
バージョン
この項では、iprofファイル形式のバージョンについて説明します。iprof形式は、iprofファイルを使用するユーザーが、どの情報がどの形式で提供されるかを把握できように、セマンティック・バージョニング・スキーム(major.minor.patch
)を使用します。メジャー・バージョンは、互換性に影響する変更(情報のエンコード方法の変更など)の場合に更新され、マイナー・バージョンは互換性に影響しない変更(最上位レベルのJSONオブジェクトへの新しいオプション・フィールドの追加など)の場合に更新され、パッチ・バージョンは、クライアントに影響を与えない軽微な修正の場合に更新されます。iprofファイル形式の現在のバージョンは1.0.0
で、サンプル・アプリケーションのiprofファイルで確認できます。
...
"version": "1.0.0",
...
型
iprofファイルのこのエントリには、プロファイルを理解するために必要なすべての型に関する情報が含まれています。これには、プリミティブ型、プロファイリングされたメソッドを宣言する型、およびそれらのメソッドのシグネチャに記載されている型が含まれますが、これらに限定されません。
iprofファイルのtypes
フィールドは、オブジェクトのJSON配列で、配列の各要素は1つの型を表します。
各型は、型オブジェクトのname
フィールドに格納されている完全修飾名で一意に識別されます。iprof形式は、ユーザーがコンテキスト外でiprofファイルを使用(たとえば、あるアプリケーションでプロファイルを収集し、完全修飾名を共有する完全に異なるタイプを持つ別のアプリケーションに適用するなど)しないことを前提としています。また、このセクションの各型は、一意のID(整数値)で識別されます。このIDは、あるiprofファイルに固有です。つまり、あるiprofファイル内のIDが3の型は、別のiprofファイルのIDが3の型と完全に異なる可能性があります。
これらのIDは、iprofファイル全体で、型を参照する必要があるときに使用されます(たとえば、メソッドの戻り値の型など。メソッドの項を参照)。これは、iprofファイルのフットプリントを削減するために行われます。毎回型の完全修飾名を参照すると、そのサイズが大幅に増加します。
フィボナッチの例のiprofファイルのtypes配列から抜粋した値を次に示します。
...
"types": [
{
"id": 0,
"name": "boolean"
},
{
"id": 1,
"name": "byte"
},
{
"id": 2,
"name": "short"
},
...
{
"id": 8,
"name": "void"
},
{
"id": 9,
"name": "java.lang.Object"
},
{
"id": 10,
"name": "Fib"
},
...
{
"id": 629,
"name": "java.lang.System"
},
...
{
"id": 4823,
"name": "[Ljava.lang.String;"
},
...
]
...
各エントリは、前述したid
とname
の2つのコンポーネントで構成されています。プリミティブ型(boolean
、byte
、short
など)、具体的な例で宣言されたFib
クラス、およびこの例で使用されている他の型(たとえば、java.lang.System
はprint
メソッドのコールに使用されます)はすべてリストに存在します。
ノート: 「具体的な例」は非常に小さいものですが、iprofファイルには(主にJDKから)合計5927個の型が含まれているため、ここでは選択した型のみを示しています。
メソッド
iprofファイルのこのエントリには、プロファイルを理解するために必要なすべてのメソッドに関する情報が含まれています。これには、アプリケーションのインストゥルメンテーション構築中にインストゥルメントされたすべてのメソッドが含まれますが、これに限定されません。インストゥルメンテーションされていないメソッドも含めることができます。たとえば、通常、プロファイルがインストゥルメンテーションではなくサンプリングによって収集される場合などです。
型と同様に、メソッドは(1つのiprofファイル内で)整数IDで一意に識別され、このIDはメソッドを参照するためにiprofファイル全体で使用されます。型とは異なり、名前(名前もiprofファイルに格納されている)だけでグローバルに識別することはできません。このため、iprofファイルにはメソッドのシグネチャ情報も格納されます。この情報は、メソッド・オブジェクトのsignature
フィールドに格納され、整数の配列としてモデル化されます。これらの整数値はそれぞれ、iprofファイルのtypes
エントリ内に存在する必要がある型のIDです。この配列の値の順序は重要です。最初の値はメソッドを宣言する型、2番目の値はメソッドの戻り型、残りの値はメソッドの順序内パラメータの型です。レシーバの型はシグネチャの一部ではないことに注意してください。
アプリケーションのiprofファイルの例から抜粋した次のメソッドについて考えてみます:
"methods": [
...
{
"id": 19547,
"name": "main",
"signature": [
10,
8,
4823
]
},
...
{
"id": 19551,
"name": "fibonacci",
"signature": [
10,
8
]
},
]
...
各メソッド・オブジェクトは、id
、name
およびsignature
の3つのコンポーネントで構成されます。名前がmain
のメソッドのIDは19547
です。signature
フィールドの値は、10
、8
および4823
です。これにより、main
メソッドがIDが10
の型で宣言されていることがわかります。「Types」セクションに示されている例を確認すると、実際にFib
クラスであることがわかります。2番目の値は、メソッドの戻り値を示し、void
(IDは8
)です。最後の値(4823
)は、main
の単一のパラメータの型のID (java.lang.String
の配列)です。
コールカウント・プロファイル
この項では、iprof内のプロファイルの中で、最もシンプルなものと言えるコールカウント・プロファイルについて説明します。このプロファイルには、すべてのインライン・コンテキストでメソッドが実行された回数に関する情報が保持されます。つまり、iprofファイルには、インストゥルメントされたメソッドごとのカウントだけでなく、対象のメソッドが別のメソッドにインライン化されたケースごとのカウントも含まれています。このインライン化情報は「部分コール・コンテキスト」または単に「コンテキスト」と呼ばれ、この概念を理解することは、iprofファイル内にどれだけのデータが格納されているかを理解するために不可欠です。
部分コール・コンテキスト
部分コール・コンテキストは、コード内の特定の場所に対する複数のレベルのコール元メソッドを記述し、各部分コール・コンテキストに異なるプロファイルを割り当てることができます。部分コール・コンテキストの長さは任意に選択でき、コール元なしで1つのコードの場所を常に指定することもできます(つまり、常にコンテキストに依存しないコードの場所を使用)。
これらのコンテキストは、関連するプロファイルを正しい場所に適用できるように、コード内の特定の場所を識別します。概要レベルでは、コンテキストはメソッドおよびバイトコード索引(BCI)の順序付きリストであり、このリストは、プロファイルがメソッドb
にインライン化されたBCI x
のメソッドa
に関連し、呼出しがBCI y
であったことなどを示します。
次のJavaプログラムの例、特にプログラムのコール・グラフについて考えてみます。
public class EvenOrOddLength {
public static void main(String[] args) {
printEvenOrOdd(args[0]);
}
private static void printEvenOrOdd(String s) {
if (s.length() % 2 == 0) {
printEven();
} else {
printOdd();
}
}
private static void printEven() {
print("even");
}
private static void printOdd() {
print("odd");
}
private static void print(String s) {
System.out.println(s);
}
}
このプログラムには、次の不完全なコール・グラフがあります。このグラフでは、ボックスはメソッド(iprofファイルに従った名前とID付き)で、「BCI上のコール」関係を表すラベル付き矢印で接続されています。
BCI 2 +----------+ BCI 9
+-----------| printEven|<-----------+
| | ID 3 | |
V +----------+ |
+-------+ +----------------+ BCI 3 +------+
| print | | printEvenOrOdd |<----------| main |
| ID 5 | | ID 2 | | ID 1 |
+-------+ +----------------+ +------+
^ |
| +---------+ |
+---------- | printOdd|<------------+
BCI 2 | ID 4 | BCI 15
+---------+
最も単純な部分コンテキストの例は、インライン化されていないメソッドの開始です。これは、メソッドがインライン化されなかったことを意味するのではなく、単に、このコンテキストではコンパイル・ルートとして機能しているということです。この情報は、:
で区切られた整数のペアとして格納されます。この2つの整数のうち、1つ目は(前に説明したように)メソッドIDで、2つ目はBCIです。この例はメソッドの開始に関するものであるため、BCIは0になります。このサンプル・アプリケーションでは、このような単一メソッドの開始部分の部分コンテキストの例は、BCI 0のmain
、ID:BCI表記では1:0
です。
単一メソッド部分コンテキスト内の追加の場所を識別する必要がある場合は、main
のBCI 3の場所を示す1:3
のような部分コンテキストを使用できます。コール・グラフは、このコンテキストがprintEvenOrOdd
の呼出しに対応していることを示しています。
次に、メソッドが別のメソッドにインライン化されたコンテキストについて考えてみます。このサンプル・アプリケーションのコンパイルでは、コンパイルがmain
から開始されるとします。また、インライナがprintEvenOrOdd
のコールをmain
(BCI 3)にインライン化することを決定するとします。コール・グラフ上に重ねられたコンパイル・ユニットは次のようになります。
BCI 2 +----------+ BCI 9
+-----------| printEven|<-----------+
| | ID 3 | |
V +----------+ +----------|-----------------------------+
+-------+ | +----------------+ BCI 3 +------+ |
| print | | | printEvenOrOdd |<----------| main | |
| ID 5 | | | ID 2 | | ID 1 | |
+-------+ | +----------------+ +------+ |
^ +----------|-----------------------------+
| +---------+ |
+---------- | printOdd|<------------+
BCI 2 | ID 4 | BCI 15
+---------+
BCI 3でmain
にインライン化された場合のprintEvenOrOdd
の開始と表すことのできる場所を識別する必要があります。コンテキストは、前述の例と同じように開始されます。メソッドのID (printEvenOrOdd
の場合は2)、続いて:
およびBCI (メソッドの先頭は0)が続きます。ただし、追加のコンテキスト情報(printEvenOrOdd
がBCI 3のmain
にインライン化されたという事実)をエンコードする必要もあります。これを行うために、コンテキストは<
文字を付加し、次に追加のコンテキストを付加します。この結果のコンテキストは、2:0<1:3
(BCI 0のID 2のメソッドが、BCI 3のID 1のメソッドにインライン化)と記述されます。同様に、このコンパイル・ユニットからのprintEven
(printEvenOrOdd
のBCI 9にあります)のコールは、2:9<1:3
と記述できます。
このコンパイル・ユニットを拡張して、さらにいくつかのメソッドを含めてみましょう。print
メソッドはBCI 3のprintEven
にインライン化され、これはBCI 9のprintEvenOrOdd
にインライン化され、これはBCI 3のmain
にインライン化されます。拡張コンパイル・ユニットを次のグラフに示します。
+------------------------------------------------------------------------+
| BCI 2 +----------+ BCI 9 |
| +-----------| printEven|<----------- |
| | | ID 3 | | |
| V +----------+ | |
| +-------+ +----------------+ BCI 3 +------+ |
| | print | | printEvenOrOdd |<----------| main | |
| | ID 5 | | ID 2 | | ID 1 | |
| +-------+ +----------------+ +------+ |
+------^-----------------------------------|-----------------------------+
| +---------+ |
+---------- | printOdd|<------------+
BCI 2 | ID 4 | BCI 15
+---------+
自然言語で記述するには非常に煩雑な、複数の部分コンテキストを、より簡潔に記述できるようになりました。5:0<3:2<2:2<1:3
部分コンテキストについて考えてみます。これは、print
の開始がBCI2のprintEven
にインライン化され、それがBCI 9のprintEvenOrOdd
にインライン化され、それがBCI 3のmain
にインライン化されると解釈されます。これらの部分コンテキストは、インストゥルメントされたイメージの構築中にコンパイラが行ったインライン化の決定に応じて、任意の長さになることがあります。
このコンパイル・ユニットにはprintOdd
は含まれません。ここで、printOdd
がコンパイル・ルートで、ここにBCI 2でprint
がインライン化されるとします。両方のコンパイル・ユニットをコール・グラフに重ね合せると、次のようになります。
+------------------------------------------------------------------------+
| BCI 2 +----------+ BCI 9 |
| +-----------| printEven|<----------- |
| | | ID 3 | | |
+--|------V------+ +----------+ | |
| | +-------+ | +----------------+ BCI 3 +------+ |
| | | print | +-----------------+ | printEvenOrOdd |<----------| main | |
| | | ID 5 | | | ID 2 | | ID 1 | |
| | +-------+ | +----------------+ +------+ |
| +------^-----------------------------------|-----------------------------+
| | +---------+ | |
| +---------- | printOdd|<------------+
| BCI 2 | ID 4 | | BCI 15
| +---------+ |
+----------------------------------+
これにより、「print
の開始」に2つの異なる部分プロファイルが作成されます。1つは前述のコンテキスト(5:0<3:2<2:2<1:3
)で、もう1つは部分コンテキスト(5:0<4:2
)の最右端のエントリとしてprintOdd
があります。print
がコンパイル・ルートとしてもコンパイルされている場合(たとえば、コード内の別のポイントからコールされ、そこにインライン化されていない場合)、print
の開始にはさらに別の部分コンテキストが存在し、これは単に5:0
となります。
コール・カウント・プロファイルの保存
iprofファイルのこのエントリは、オブジェクトの配列で、各オブジェクトにはコンテキスト(オブジェクトのctx
フィールドに格納)と、プロファイルの実際の数値(オブジェクトのrecords
フィールドに格納)が含まれます。コールカウント・プロファイルの場合、格納される数値は、そのコンテキストでメソッド(コンテキストの開始時、BCI 0)が実行された回数のみです。これは、単一の値を持つ整数の配列としてモデル化されます。
1つ目のアプリケーション例のコールカウント・プロファイルの例を次に示します。
"callCountProfiles": [
...
{
"ctx": "19551:0",
"records": [
1
]
},
...
{
"ctx": "4669:0<19551:34",
"records": [
10
]
},
...
]
最初に示されたオブジェクトは、IDが19551
のメソッドがそのコンテキストで1回のみ実行されたことを示します。iprofファイルのmethods
フィールドでそのIDのメソッドを検索すると、Fib
クラスのfibonacci
メソッドであることが示されます。このメソッドは実行中に1回のみ実行され、偶然、唯一のコール元(main
)にインライン化されませんでした。
2つ目のオブジェクトは、IDが4669
のメソッドがfibonacci
にインライン化され、コールがBCI 34上であったことを示しています。このメソッドは、そのコンテキストで10回実行されました。iprofファイルでさらに調べると、これは実際にはSystem.out
を介して呼び出されたjava.io.PrintStream#print
メソッドであり、そのコンテキストで実際に10回実行されたことがわかります。この確認は読者の課題とします。
条件プロファイル
条件プロファイルには、コード内の条件(分岐)の動作に関する情報が含まれます。これには、if
文とswitch
文、およびすべてのループが含まれます。これは、最終的に条件文によってバインドされるためです。プロファイル情報は、基本的には、条件文の各分岐が実行された回数です。
条件プロファイルは、コールカウント・プロファイルと非常によく似た方法で格納されます。つまり、ctx
およびrecords
フィールドを持つオブジェクトの配列で、その値はそれぞれ文字列および整数の配列です。コールカウント・プロファイルの情報、特に「部分コール・コンテキスト」の情報を理解することをお薦めします。
フィボナッチの例から抜粋した次の条件プロファイルについて考えてみます。
"conditionalProfiles": [
...
{
"ctx": "19551:11",
"records": [
20,
0,
10,
53,
1,
1
]
},
...
]
このオブジェクトのctx
フィールドの値は、対象のメソッドのIDが19551
(Fib#fibonacci
)であることを示しています。対象のBCIは11です。メソッドのバイトコードを調べると、BCI 11がfibonacci
メソッドのfor
ループの条件チェックに対応していることがわかります。つまり、このプロファイルはfibonacci
メソッドのfor
ループに関するものです。このオブジェクトのrecords
エントリは、6つの値の配列です。これは、条件に2つの分岐(1つはループの開始、もう1つはループの終了)があり、分岐ごとに3つの整数値(分岐のジャンプ先のBCI、分岐の索引および分岐が実行された回数)が格納されるためです。つまり、条件プロファイルのrecords
配列の長さは常に3で割り切れます。100個の分岐を持つswitch文は、300個の値の配列になります。分岐の索引は、コンパイラによって課される分岐の順序にすぎません。複数の分岐が同じBCIをターゲットにできますが、索引は一意であるため、これが必要です。
例の値(20
、0
、10
、53
、1
、1
)に戻ると、BCI 20(索引0)へのジャンプが10回(最初の3つの値)発生し、BCI 53 (索引1)へのジャンプが1回発生したことを示しています。fibonacci
のソース・コードを参照すると、ループはn
回(この例では10回)実行されます。これは収集されたプロファイルと一致しています。ループの先頭にジャンプしてループを10回繰り返す10と、ループの外側にジャンプしてループを終了する1です。
仮想呼出しプロファイル
仮想呼出しプロファイルには、仮想呼出しのレシーバの実行時の型に関する情報が含まれます。具体的には、記録された各型が仮想コールのレシーバの型であった回数です。PGOの現在の実装では、場所ごとに記録される型の数が8に制限されますが、iprof形式ではそのような制限はありません。
仮想呼出しプロファイルは、コールカウント・プロファイルと非常によく似た方法で格納されます。つまり、ctx
およびrecords
フィールドを持つオブジェクトの配列で、その値はそれぞれ文字列および整数の配列です。コールカウント・プロファイルの情報、特に「部分コール・コンテキスト」の情報を理解することをお薦めします。
フィボナッチの例から抜粋した次の仮想呼出しプロファイルについて考えてみます。
...
"virtualInvokeProfiles": [
...
{
"ctx": "3236:11<4669:2<19551:34",
"records": [
2280,
10
]
},
...
{
"ctx": "6886:9<6882:23",
"records": [
1322,
2,
2280,
60,
3660,
56
]
},
...
]
...
コンテキストの最後にあるメソッドのIDは19551 (Fib#fibonacci
)です。このメソッドでは、BCI 34でID 4669のメソッドがfibonacci
にインライン化されています。iprofファイル内のメソッドを確認すると、これはソース・コードから予想されるjava.io.PrintStream#print
であることがわかります。さらに、BCI 2では、ID 3239のメソッドがprint
にインライン化され、プロファイルはそのメソッドのBCI 11
を参照しています。iprofファイル内のメソッドを再度確認すると、メソッドjava.lang.String#valueOf(java.lang.Object)
のIDが3236であることがわかります。このvalueOf
メソッドには、BCI 11
で仮想呼出しがあります。このメソッドのソース・コードは次のとおりです。この仮想呼出しは、Object
に対するtoString
のコールです。
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
records
配列には2つの値のみが含まれます。最初の数字は、記録された型のIDです(この例では2280
はjava.lang.String
です)。2番目の数値は、この型がこの仮想呼出しのレシーバとなった回数です。サンプル・アプリケーションはjava.lang.String
のみをprint
メソッドに渡し(num1
の後にスペースを追加すると、引数がjava.lang.String
に暗黙的に変換されることに注意してください)、print
メソッドが10回コールされるため、java.lang.String
のカウントは10です。
仮想呼出しプロファイルのrecords
配列の長さは、型IDとカウントのペアを表すため、常に2の倍数になります。この例の2番目のオブジェクトでは、records
配列に6つのエントリがあり、実行時に3つの異なる型がレシーバの型として記録されたことを意味します。
モニター・プロファイル
この項では、モニター・プロファイルについて説明します。Javaでは、各オブジェクトに独自のモニターがあり、(synchronized
キーワードを使用して)コードのセクションへの排他的アクセスを保証するために使用できます。モニター・プロファイルは、コードの同期(synchronized
を型のメソッドに追加することで暗黙的に、またはsynchronized(obj) {...}
で明示的に)に使用された型と、各型で同期が何回行われたかを記録します。
モニター・プロファイルは、コールカウント・プロファイルと非常によく似た形式で格納されます。つまり、ctx
およびrecords
フィールドを持つオブジェクトの配列で、その値はそれぞれ文字列および整数の配列です。コールカウント・プロファイルの情報、特に部分コール・コンテキストの情報を理解することをお薦めします。
モニター・プロファイルはグローバルである、つまり特定のコンテキストとは関係がないため、配列には1つのオブジェクトのみがあり、そのオブジェクトにはctx
フィールドにダミーの0:0
コンテキストがあります。これは、すべてのプロファイルの形式を一致させるためのレガシーな理由によるものです。
フィボナッチの例のモニター・プロファイルの全体を次に示します。
"monitorProfiles": [
{
"ctx": "0:0",
"records": [
9,
4,
10,
1,
579,
9,
619,
10,
1213,
1,
1972,
1,
2284,
2,
2337,
1,
2612,
2,
3474,
3,
3654,
61,
3807,
3,
3820,
7,
4060,
2,
4127,
3,
4725,
6
]
}
],
...
]
前述のように、配列内の単一オブジェクトのctx
フィールドの値は、ダミー・コンテキスト0:0
です。一方、records
は、仮想呼出しプロファイルに使用される形式(型IDとカウントのペアの配列)に似ています。つまり、仮想呼出しプロファイルと同様に、records
配列の長さは2の倍数であるということです。
配列の最初の2つの値は、IDが9
(java.lang.Object
)の型が同期に4回使用されたことを示しています。この例では、Fib
のインスタンスで同期が1回のみ(fibonacci
メソッドがsynchronized
される)であるため、次の2つの値は、ID 10
(Fib
)の型が同期に1回使用されたことを示します(fibonacci
メソッドは1回のみ実行されることを思い出してください)。
サンプリング・プロファイル
この項では、サンプリング・プロファイルについて説明します。これまで説明したすべてのプロファイルはインストゥルメンテーションを介して収集され、部分的なコンテキストのみを持ちますが、サンプリング・プロファイルはこれらとは異なり、定期的にコール・スタックをサンプリングすることによって収集され、インストゥルメンテーションは必要ありません。これは、サンプリング・プロファイルに含まれるコンテキストは部分的なものではなく、サンプリング時点のコール・スタック全体であることも意味します。つまり、他のプロファイルと比較してサンプリング・プロファイルのコンテキストがはるかに長くなることは正常で、想定内です。
サンプリング・プロファイルは、コールカウント・プロファイルと非常によく似た方法で格納されます。つまり、ctx
およびrecords
フィールドを持つオブジェクトの配列で、その値はそれぞれ文字列および整数の配列です。コールカウント・プロファイルの情報、特に「部分コール・コンテキスト」の情報を理解することをお薦めします。
サンプラーが有用な様々なサンプルを収集できるようフィボナッチの例を実行し、サンプリング・プロファイル全体を次に示します。
...
"samplingProfiles": [
{
"ctx": "11823:38<12811:1<12810:33<12855:25<19551:17<19547:9<19529:10<6305:105<5998:67<5941:0<5903:50<2684:23<2685:1",
"records": [
10
]
},
{
"ctx": "22500:23<22353:65<22210:15<22187:246<22032:20<22030:1<22027:22<11795:68<11793:12<43854:2",
"records": [
1
]
}
],
...
]
...
サンプリング・プロファイルでは、ctx
値の長さがはるかに長くなります。サンプリング・プロファイルの最初のオブジェクトには、コンテキストの先頭にIDが11823
のメソッドがあります。iprofファイルのメソッド・エントリを確認すると、これはcom.oracle.svm.core.thread.PlatformThreads#sleep
メソッドで、ID 12811
(java.lang.Thread#sleepNanos0
)のメソッドからコールされ、これがID 12810
(java.lang.Thread#sleepNanos
)のメソッドからコールされ、それがID 12855
(java.lang.Thread#sleep
)のメソッドからコールされ、さらにID 19551
(Fib#fibonacci
)のメソッドからコールされ、というようにアプリケーションのエントリ・ポイントまで続きます。他のプロファイルが使用している部分的なものとは異なり、これは完全なコンテキストです。
records
配列には、実行時サンプリング中にこの一意のコール・スタックが何回使用されたかを示す単一の値が含まれます。この例では、前の段落で説明したコンテキストが10回記録されたことを意味します。
サンプリング・プロファイル配列のもう1つのオブジェクトには異なるコンテキストが含まれ、このサンプルは1回のみ使用されました。このサンプルの性質を理解することは読者の課題とします。