データ収集と解析の準備のために、プログラムに対して特別な作業を行う必要はありません。次の処理のうち、いずれか 1 つでも行うプログラムの場合には、後述の該当する節を読んでください。
シグナルハンドラをインストールする
システムライブラリを明示的かつ動的に読み込む
関数を動的にコンパイルする
プロファイルする派生プロセスを作成する
非同期 I/O ライブラリを使用する
プロファイルタイマーまたはハードウェアカウンタ API を直接使用する
setuid(2) を呼び出すか、setuid ファイルを実行する
また、データ収集をプログラムから制御する場合も、該当する節を読んでください。
多くのプログラムは、次のような機能を使用して、動的に割り当てられたメモリーに依存しています。
malloc、valloc、alloca (C/C++)
new (C++)
スタック局所変数 (Fortran)
MALLOC、MALLOC64 (Fortran)
メモリー割り当ての方式の説明で、初期値が設定されることが明示的に記述されている場合を除き、動的に割り当てられるメモリーの初期値にプログラムが依存しないよう注意する必要があります。例としては、malloc (3C) のマニュアルページにある calloc と malloc の説明を比較してください。
動的に割り当てられるメモリーを使用するプログラムを単独で実行すると、正常に機能しているように見えることがありますが、パフォーマンスデータの収集を有効にした状態で実行すると、問題が起きることがあります。この場合、浮動小数点演算の予期しない動作、セグメント例外、アプリケーション固有のエラーメッセージなどが発生する可能性があります。
こうした症状は、アプリケーションが単独で実行されたときには、初期化されていないメモリーの値が動作に影響ないものであっても、パフォーマンスデータの収集ツールとの組み合わせで実行されたときに別の値が設定されることによって、発生する場合があります。この場合は、パフォーマンスツールの問題ではありません。動的に割り当てられるメモリーの内容に依存するアプリケーションにはすべて、潜在的なバグがあります。オペレーティングシステムにより動的に割り当てられるメモリーの内容は、ドキュメントに明確に記載されている場合を除いて、どのような値をとる可能性もあります。現在のオペレーティングシステムが動的に割り当てられるメモリーに必ず特定の値を設定するようになっていたとしても、将来オペレーティングシステムのリリースが変わったとき、あるいはプログラムを別のオペレーティングシステムに移植した場合には、こうした潜在的な問題によって、予期しない動作が発生する可能性があります。
次のツールが、こうした潜在的な問題の発見に役立ちます。
f95 -xcheck=init_local
詳細は、『Fortran ユーザーズガイド』または f95(1) のマニュアルページを参照してください。
lint ユーティリティー
詳細は、『C ユーザーズガイド』または lint(1) のマニュアルページを参照してください。
dbx 下での実行時チェック
詳細は、『dbx コマンドによるデバッグ』または dbx(1) のマニュアルページを参照してください。
Rational Purify
さまざまなコレクタは、システムライブラリの関数に割り込んでトレースデータを収集し、データ収集の整合性を保証します。コレクタがライブラリ関数の呼び出しに割り込む状況を次に示します。
同期待ちトレースデータの収集。Solaris 10 OS では、コレクタは Solaris C ライブラリ libc.so からの関数に割り込みます。
ヒープトレースデータの収集。コレクタは、malloc、realloc、memalign、free の関数に割り込みます。これらの関数は、C 標準ライブラリ libc.so のほか、libmalloc.so や libmtmalloc.so などのライブラリにあります。
時間データの完全性の確保。コレクタは setitimer に割り込み、プログラムがプロファイルタイマーを使用しないようにします。
ハードウェアカウンタデータの完全性の確保。コレクタは、ハードウェアカウンタライブラリ libcpc.so からの関数に割り込み、プログラムがカウンタを使用しないようにします。プログラムからこのライブラリの関数への呼び出しは、値 -1 を返します。
派生プロセスに対するデータ収集の有効化。コレクタは、fork(2)、fork1(2)、vfork(2)、fork(3F)、posix_spawn(3p)、posix_spawnp(3p)、system(3C)、system(3F)、sh(3F)、popen(3C)、exec(2) の関数とそのバリアントに割り込みます。vfork の呼び出しは、内部で fork1 の呼び出しに置き換えられます。これらの割り込み処理が行われるのは、collect コマンドの場合だけです。
コレクタによる SIGPROF シグナルおよび SIGEMT シグナルの処理の保証。コレクタは sigaction に割り込み、シグナルハンドラがこれらのシグナル用のプライマリシグナルハンドラであるかどうかを確認します。
次のような環境では、割り込みが成功しません。
割り込み対象関数が入っているライブラリとプログラムを静的にリンクした場合。
コレクタライブラリが事前読み込みされていない実行中アプリケーションに dbx を接続した場合。
これらのライブラリのいずれか 1 つを動的に読み込み、このライブラリの中でだけ検索することによってシンボルを解決する場合。
コレクタが割り込み処理を行えなかった場合には、パフォーマンスデータが消去されたり無効になったりする可能性があります。
er_sync.so、er_heap.so、および er_mpviewn.so (n は MPI バージョンを示す) ライブラリは、それぞれ同期待ちトレースデータ、ヒープトレースデータ、または MPI トレースデータが要求された場合のみ読み込まれます。
コレクタは、2 つのシグナルを使用して、プロファイルデータを収集します。SIGPROF はすべての実験に使用され、SIGEMT (Solaris プラットフォームの場合) または SIGIO (Linux プラットフォームの場合) はハードウェアカウンタ実験にのみ使用されます。コレクタは、これらの各シグナルについてシグナルハンドラをインストールします。シグナルハンドラは自身のシグナルをインターセプトして処理しますが、ほかのシグナルは、インストールされているほかのシグナルハンドラに引き渡します。プログラムがこれらのシグナル用に独自のシグナルハンドラをインストールすると、コレクタは自分のシグナルハンドラをプライマリハンドラとして再インストールし、それによって完全なパフォーマンスデータが確保されます。
collect コマンドでは、ユーザー指定のシグナルを使用してデータ収集の一時停止と再開、および標本の記録を行えます。それらのシグナルはコレクタによって保護されませんが、ユーザーハンドラがインストールされている場合は、実験に警告が書き出されます。コレクタとアプリケーションによる指定シグナルの使用が互いに競合しないように、ユーザーが責任を持って確認する必要があります。
コレクタによってインストールされたシグナルハンドラは、システムコールがシグナル配信のために中断されないようにするためのフラグを設定します。このフラグ設定方法では、プログラムのシグナルハンドラがシステムコールの中断を許可するようにフラグを設定した場合に、プログラムの動作が変化する可能性があります。動作が変化する重要な例として、非同期キャンセル処理に SIGPROF を使用し、システムコールの中断を行う非同期入出力ライブラリ libaio.so が挙げられます。コレクタライブラリ libcollector.so がインストールされている場合、キャンセルシグナルの到着は常に、非同期入出力操作を取り消すには遅すぎます。
コレクタライブラリを事前読み込みしないままプロセスに dbx を接続してパフォーマンスデータ収集を有効にし、そのあとでプログラムが自分のシグナルハンドラをインストールすると、コレクタは自分のシグナルハンドラを再インストールしません。この場合、プログラムのシグナルハンドラは、パフォーマンスデータが失われないように、SIGPROF および SIGEMT シグナルが確実に渡されるようにする必要があります。プログラムのシグナルハンドラがシステムコールを中断した場合、プログラムの動作とプロファイルの動作は、コレクタライブラリが事前読み込みされた場合の動作と異なります。
動的ローダーによって課される制約により、setuid(2) を使用してパフォーマンスデータを収集することが困難になります。プログラムが setuid を呼び出すか、setuid ファイルを実行する場合、コレクタは新しいユーザー ID に必要なアクセス権がないために、実験ファイルに書き込めない可能性が高くなります。
collect コマンドは、ターゲットのアドレス空間 (LD_PRELOAD) に共有ライブラリ libcollector.so を挿入して動作します。実行可能ファイルが setuid または setgid を呼び出す、または setuid または setgid を呼び出す子孫プロセスを作成する場合、その実行可能ファイルから collect コマンドを起動すると、いくつかの問題が発生する可能性があります。root ではないユーザーとして実験を実行すると、共有ライブラリが信頼できるディレクトリにインストールされていないために収集が失敗します。対策としては、root ユーザーとして実験を実行するか、crle(1) を使用してアクセス権を与えます。セキュリティバリアーを迂回するときには十分な注意を払い、各自のリスクで行ってください。
collect コマンドを実行するとき、umask は、自分、exec() によって実行されるプログラムの setuid 属性および setgid 属性によって設定されているユーザーまたはグループ、およびプログラム自体によって設定されるユーザーまたはグループに対して、書き込みアクセスを許可するように設定されている必要があります。マスクが正しく設定されていない場合、一部のファイルが実験に対して書き込まれず、実験が処理不能になることがあります。ログファイルが書き込み可能な場合、実験の処理を試みたときにエラーが示されます。
ターゲット自身が UID または GID を設定する何らかのシステムコールを発行した場合、自分の umask を変更してから fork を行うか、ほかの実行可能ファイルを exec() で実行した場合、または crle を使用してランタイムリンカーが共有オブジェクトを検索する方法が構成された場合には、ほかの問題が発生する可能性があります。
実効 GID を変更するターゲット上で実験が root として開始された場合、実験の終了時に実行される er_archive プロセスが失敗します。これは、このプロセスには「信頼できる」にマークされていない共有ライブラリが必要なためです。この場合、実験の終了直後に、実験が記録されたマシン上で、er_archive ユーティリティー (または er_print ユーティリティーか analyzer コマンド) を手作業で明示的に実行できます。
プログラムからデータ収集を制御するために、コレクタ共有ライブラリ libcollector.so に含まれているいくつかの API 関数を使用できます。これらの関数は C で記述されており、Fortran インタフェースも用意されています。ライブラリとともに提供されるヘッダーファイルに、C インタフェースと Fortran インタフェースの両方が定義されています。
API 関数は、次のように定義されます。
void collector_sample(char *name); void collector_pause(void); void collector_resume(void); void collector_terminate_expt(void); |
CollectorAPI クラスに、Java プログラム用の類似の機能が用意されており、これについては、「Java インタフェース」で説明しています。
collectorAPI.h を取り込み、配下の libcollector.so API 関数の存在をチェックする実際の関数を含む、-lcollectorAPI にリンクすることにより、コレクタ API の C/C++ インタフェースにアクセスできます。
有効な実験がない場合、API 呼び出しは無視されます。
Fortran API の libfcollector.h ファイルは、ライブラリへの Fortran インタフェースを定義します。このライブラリを使用するには、アプリケーションを -lcollectorAPI にリンクする必要があります。このライブラリには、下位互換性を維持するため、-lfcollector というもう 1 つの名前も用意されています。動的関数とスレッドによる呼び出しの一時停止と再開を除けば、Fortran API は C/C++ API と同じ機能を提供します。
Fortran の場合、API 関数を使用するには、次の文を挿入します。
include "libfcollector.h" |
どんな言語を使用している場合も、プログラムを -lcollector とリンクしないでください。リンクした場合、コレクタが予期しない動作をすることがあります。
次の文を使用して、CollectorAPI クラスをインポートし、Java API にアクセスできます。ただし、アプリケーションは / installation_directory/lib/collector.jar (installation_directory は Oracle Solaris Studio ソフトウェアがインストールされているディレクトリです) をクラスパスが指している状態で呼び出される必要があります。
import com.sun.forte.st.collector.CollectorAPI; |
Java CollectorAPI メソッドは、次のように定義されます。
CollectorAPI.sample(String name) CollectorAPI.pause() CollectorAPI.resume() CollectorAPI.terminate() |
Java API には、動的関数 API 以外の C および C++ API と同じ関数が含まれています。
C インクルードファイルの libcollector.h には、データが収集されていないときには実際の API 関数への呼び出しを迂回するマクロが含まれています。この場合、関数は動的に読み込まれません。ただし、一部の条件では適切に機能しないことがあるため、これらのマクロを使用するのは危険です。collectorAPI.h はマクロを使用していないため、このファイルを使用する方が安全です。このファイルでは、関数が直接参照されます。
Fortran API サブルーチンはパフォーマンスデータが収集されているときには C API 関数を呼び出し、そうでないときには復帰します。チェック処理のオーバーヘッドは非常に小さいので、プログラムのパフォーマンスにはあまり影響がないはずです。
パフォーマンスデータを収集するには、この章で後述するように、コレクタを使用してプログラムを実行する必要があります。API 関数への呼び出しを挿入することによって、データ収集が有効になることはありません。
マルチスレッドプログラムで API 関数を使用する場合は、これらの関数が 1 つのスレッドによってのみ呼び出されるようにする必要があります。API 関数は、個別のスレッドではなくプロセスに適用される動作を実行します。各スレッドが API 関数を呼び出すと、記録されたデータが期待したものにならない可能性があります。たとえば、あるスレッドが collector_pause() や collector_terminate_expt() を呼び出したときに、ほかのスレッドがまだプログラム内のそのポイントに達していない場合、すべてのスレッドについて収集が一時停止または停止され、この API 呼び出しの前にコードを実行していたスレッドのデータが失われる可能性があります。
ここでは、データ収集に関係する API 関数について説明します。
C および C++: collector_sample(char *name)
Fortran: collector_sample(string name)
Java: CollectorAPI.sample(String name)
標本パケットを記録し、その標本に指定された名前または文字列をラベルとして付けます。ラベルは、「パフォーマンスアナライザ」の「イベント」タブで表示されます。Fortran の引数 string の型は、character です。
標本ポイントに含まれるデータは、プロセスに関するものであり、個々のスレッドに関するものではありません。マルチスレッドアプリケーションの場合、collector_sample() API 関数は、標本の記録中に別の呼び出しが行われても、1 つの標本だけが書き込まれるようにします。記録される標本の数は、呼び出しを行うスレッドの数よりも少なくなります。
パフォーマンスアナライザは、別々のメカニズムによって記録された標本同士を区別しません。API 呼び出しによって記録された標本だけを見たい場合には、パフォーマンスデータの記録時にほかのあらゆる標本モードを停止します。
C、C++、Fortran: collector_pause()
Java: CollectorAPI.pause()
実験へのイベント固有データの書き込みを停止します。実験はオープン状態のままであり、大域データの書き込みは続けられます。有効な実験がない場合やデータの記録がすでに停止されている場合には、呼び出しは無視されます。この関数は、たとえすべてのイベント固有データの書き込みが collector_thread_resume() 関数によって特定のスレッドに対して有効にされていたとしても、その書き込みを停止します。
C、C++、Fortran: collector_resume()
Java: CollectorAPI.resume()
collector_pause() を呼び出したあとに、実験へのイベント固有データの書き込みを再開します。有効な実験がない場合やデータの記録が有効である場合には、呼び出しは無視されます。
C、C++、Fortran: collector_terminate_expt()
Java: CollectorAPI.terminate
データの収集対象である実験を終了します。それ以後のデータは収集されませんが、プログラムの実行は正常に続行されます。有効な実験がない場合は、呼び出しは無視されます。
使用している C または C++ プログラムが、関数を動的にコンパイルしてデータ空間に取り込む場合、動的関数やモジュールのデータをパフォーマンスアナライザに表示するには、コレクタに情報を与える必要があります。この情報は、コレクタ API 関数の呼び出しによって渡されます。API 関数の定義は、次のとおりです。
void collector_func_load(char *name, char *alias, char *sourcename, void *vaddr, int size, int lntsize, Lineno *lntable); void collector_func_unload(void *vaddr); |
Java HotSpot 仮想マシンによってコンパイルされる Java メソッドには別のインタフェースが使用されるので、これらの API 関数を使用する必要はありません。Java インタフェースは、コンパイルされたメソッドの名前をコレクタに知らせます。Java コンパイル済みメソッドの関数データと注釈付き逆アセンブリのリストを見ることはできますが、注釈付きソースリストを見ることはできません。
実験への記録のため、動的にコンパイルされた関数に関する情報をコレクタに渡します。パラメータリストを次の表に示します。
表 3–1 collector_func_load() のパラメータリスト
パラメータ |
定義 |
---|---|
name |
パフォーマンスツールで使用する、動的にコンパイルされた関数の名前。実際の関数名でなくてもかまいません。この名前は関数の通常の命名規則に従っている必要はありませんが、空白文字や引用符は含めないようにします。 |
alias |
関数の説明に使用する任意の文字列。NULL も使用できます。この文字列が解釈の対象となることはありません。空白文字を含めることができます。アナライザの「概要」タブに表示されます。何の関数であるか、またはなぜ関数が動的に構築されたかを示すために使用できます。 |
sourcename |
関数の構築元であるソースファイルのパス。NULL も使用できます。このソースファイルは、注釈付きソースリストに使用されます。 |
vaddr |
関数が読み込まれたアドレス。 |
size |
バイト数による関数のサイズ。 |
lntsize |
行番号テーブルのエントリの数を示すカウント。行番号情報がない場合には、ゼロとなります。 |
lntable |
lntsize エントリが入っているテーブル。各エントリは、整数対です。第 1 整数はオフセット、第 2 整数は行番号です。あるエントリのオフセットと次のエントリのオフセットとの間の命令はすべて、最初のエントリの行番号に対応します。オフセットは数値の昇順にする必要がありますが、行番号の順序は任意です。lntable が NULL の場合、関数のソースリストは利用できませんが、逆アセンブリリストは利用できます。 |
アドレス vaddr にある動的関数が読み込み解除されたことをコレクタに通知します。