Oracle® Solaris Studio 12.4: パフォーマンスアナライザ

印刷ビューの終了

更新: 2015 年 1 月
 
 

シングルスレッド実行と関数呼び出し

プログラムの実行でもっとも単純なものは、シングルスレッドプログラムが専用のロードオブジェクト内の関数を呼び出す場合です。

プログラムがメモリーに読み込まれて実行が開始されると、初期実行アドレス、初期レジスタセット、スタック (スクラッチデータの格納および関数の相互の呼び出し方法の記録に使用されるメモリー領域) からなるコンテキストが作成されます。初期アドレスは常に、あらゆる実行可能ファイルに組み込まれる _start() 関数の先頭位置になります。

プログラムを実行すると、たとえば関数呼び出しや条件文を表すことがある分岐命令があるまで、命令が順実行されます。分岐点では、分岐先が示すアドレスに制御が渡されて、そこから実行が続行されます。(SPARC では通常、分岐の次の命令は実行するようすでにコミットされており、この命令は分岐遅延スロット命令と呼ばれます。ただし、分岐命令には、この分岐遅延スロット命令の実行を無効にするものもあります。

呼び出しを表す命令シーケンスが実行されると、復帰アドレスがレジスタに書き込まれ、呼び出された関数の最初の命令から実行が続行されます。

ほとんどの場合は、この呼び出し先の関数に含まれる最初の数個の命令のどこかで、新しいフレーム (関数に関する情報を格納するためのメモリー領域) がスタックにプッシュされ、そのフレームに復帰アドレスが格納されます。復帰アドレスに使用されるレジスタは、呼び出された関数がほかの関数を呼び出すときに使用できます。関数から制御が戻されようとすると、スタックからフレームがポップされ、関数の呼び出し元のアドレスに制御が戻されます。

共有オブジェクト間の関数の呼び出し

共有オブジェクト内の関数が別の共有オブジェクトの関数を呼び出す場合は、同じプログラム内の単純な関数の呼び出しよりも実行が複雑になります。各共有オブジェクトには、プログラムリンケージテーブル (PLT) が 1 つあり、ここにはそのオブジェクトが参照し、そのオブジェクトの外部にあるすべての関数 (外部関数) のエントリが含まれます。最初は、PLT 内の各外部関数のアドレスは、実際には動的リンカーである ld.so 内のアドレスです。外部関数が初めて呼び出されると、制御が動的リンカーに移り、動的リンカーは、その外部関数への呼び出しを解決し、以降の呼び出しのために、PLT のアドレスにパッチを適用します。

3 つの PLT 命令のいずれか 1 つを実行しているときにプロファイリングイベントが発生した場合、PLT PC は削除され、排他的時間はその呼び出し命令に対応することになります。PLT エントリによる最初の呼び出し時にプロファイリングイベントが発生し、かつリーフ PC が PLT 命令のいずれかではない場合、PLT および ld.so のコードから発生する PC はすべて、包括的時間を集計する擬似的な関数 @plt に帰属します。各共有オブジェクトには、こういった擬似的な関数が 1 つ用意されています。LD_AUDIT インタフェースを使用しているプログラムの場合、PLT エントリが絶対にパッチされない可能性があるとともに、@plt の非リーフ PC の発生頻度が高くなることが考えられます。

シグナル

シグナルがプロセスに送信されると、各種のレジスタ操作とスタック操作が発生し、シグナル送信時にリーフ PC がシステム関数 sigacthandler() の呼び出しの復帰アドレスを示していたかのように見えます。sigacthandler() は、関数が別の関数を呼び出すのと同じようにして、ユーザー指定のシグナルハンドラを呼び出します。

パフォーマンスアナライザは、シグナル送信で発生したフレームを通常のフレームとして処理します。シグナル送信時のユーザーコードがシステム関数 sigacthandler() の呼び出し元として表示され、sigacthandler() はユーザーのシグナルハンドラの呼び出し元として表示されます。sigacthandler() とあらゆるユーザーシグナルハンドラ、さらにはそれらが呼び出すほかの関数の包括的メトリックは、割り込まれた関数の包括的メトリックとして表示されます。

コレクタは sigaction() に割り込むことによって、時間データ収集時にはそのハンドラが SIGPROF シグナルのプライマリハンドラになり、ハードウェアカウンタオーバーフローのデータ収集時には SIGEMT シグナルのプライマリハンドラになるようにします。

トラップ

トラップは命令またはハードウェアによって発行され、トラップハンドラによって捕捉されます。システムトラップは、命令から発行され、カーネルにトラップされるトラップです。すべてのシステムコールは、トラップ命令を使用して実装されます。ハードウェアトラップの例としては、命令を完了できないとき、あるいは命令がハードウェアに実装されていないときに、浮動小数点ユニットから発行されるものがあります。

トラップが発行されると、カーネルはシステムモードになります。Oracle Solaris 上では、マイクロステートは通常、ユーザー CPU 状態からトラップ状態、そしてシステム状態に切り替わります。マイクロステートの切り替わりポイントによっては、トラップの処理に費やされた時間が、システム CPU 時間とユーザー CPU 時間を合計したものとして現れることがあります。この時間は、ユーザーコード内でそのトラップを発行した命令、またはシステムコールに割り当てられます。

一部のシステムコールでは、呼び出しをできるだけ効率的に処理することが重要であるとみなされます。こうした呼び出しによって生成されたトラップを高速トラップと呼びます。高速トラップを生成するシステム関数には、gethrtime および gethrvtime があります。これらの関数ではオーバーヘッドを伴うため、マイクロステートは切り替わりません。

このほかにも、トラップをできるだけ効率的に処理することが重要であるとみなされる場合があります。 たとえば、TLB (トランスレーションルックアサイドバッファー) ミスやレジスタウィンドウのスピル/フィルの場合も、マイクロステートは切り替わりません。

TLB (translation lookaside buffer) ミス いずれの場合も、費やされた時間はユーザー CPU 時間として記録されます。ただし、システムモードに CPU モードが切り替えられたため、ハードウェアカウンタは動作していません。このため、これらのトラップの処理に費やされた時間は、なるべく同じ実験で記録された、ユーザー CPU 時間とサイクル時間の差を取ることで求めることができます。

トラップハンドラがユーザーモードに戻るケースもあります。Fortran で 4 バイトの境界に整列された整数に対し、8 バイト整数のメモリー参照を行うようなトラップです。 そのトラップハンドラのフレームがスタックに現れるため、整数のロードまたはストア命令に起因するそのハンドラの呼び出しがパフォーマンスアナライザに表示される場合があります。

命令がカーネルにトラップされると、そのトラップ命令のあとの命令の実行に長い時間がかかっているようにみえます。これは、カーネルがトラップ命令の実行を完了するまで、その命令の実行を開始できないためです。

末尾呼び出しの最適化

特定の関数で最後に実行することが別の関数の呼び出しである場合、コンパイラは常に、ある特定の最適化を実行できます。 新しいフレームを生成するのではなく、呼び出し先が呼び出し元のフレームを再利用し、呼び出し先のための復帰アドレスが呼び出し元からコピーされます。この最適化の目的は、スタックのサイズ削減、および SPARC プラットフォーム上でのレジスタウィンドウの使用削減にあります。

プログラムのソースの呼び出しシーケンスが、次のようになっていると仮定します。

A -> B -> C -> D

BC の末尾呼び出しが最適化されている場合、呼び出しスタックは、関数 A が関数 BC、および D を直接呼び出しているかのように見えます。

A -> B
A -> C
A -> D

呼び出しツリーが平坦化されます。-g オプションを指定してコードをコンパイルした場合、末尾呼び出しの最適化は、4 以上のレベルでのみ行われます。-g オプションなしでコードをコンパイルした場合、2 以上のレベルで末尾呼び出しの最適化が行われます。