インテル® VTune™ プロファイラーは、Windows* および POSIX* API のほとんどをサポートしますが、独自の同期構造を定義すると便利なことがあります。独自に構築された構造は、通常、インテル® VTune™ プロファイラーによって追跡されません。ただし、インテル® VTune™ プロファイラーでは、ユーザー定義の同期構造の統計情報を収集する同期 API が提供されています。
ユーザー定義同期 API は、再開状態で動作するスレッドごとの関数です。この関数はポーズ状態では動作しません。
同期構造は、一般に一連のシグナルとしてモデル化できます。1 つまたは複数のスレッドが、別のスレッドグループからのシグナルで、何らかのアクションを続行することがあります。スレッドがいつシグナルの待機を開始したか、そしていつシグナルが発生したかを追跡することで、同期 API はユーザー定義の同期オブジェクトを取得して、コードを理解することができます。API は、一連のプリミティブとメモリーハンドルを使用して、ユーザー定義の同期オブジェクトに関連する統計を収集します。
ユーザー定義同期 API は、スレッド化解析タイプと連携します。
次の表は、Windows* または Linux* オペレーティング・システムで利用可能なユーザー定義の同期 API プリミティブについて説明します。
使用するプリミティブ |
これを行うには |
|---|---|
void __itt_sync_create (void *addr, const __itt_char *objtype, const __itt_char *objname, int attribute) |
char または Unicode 文字列を使用する同期オブジェクトの作成を登録します。 |
| void __itt_sync_rename (void *addr, const __itt_char *name) | 作成後、char または Unicode 文字列を使用して同期オブジェクトに名前を割り当てます。 |
void __itt_sync_destroy (void *addr) |
破棄されたオブジェクトのライフタイムを追跡します。 |
void __itt_sync_prepare (void *addr) |
ユーザー定義の同期オブジェクトでスピンループに入ります。 |
void __itt_sync_cancel (void *addr) |
スピン・オブジェクトを取得せずにスピンループを出ます。 |
void __itt_sync_acquired (void *addr) |
スピンループの正常終了を定義します (同期オブジェクトの取得)。 |
void __itt_sync_releasing (void *addr) |
同期オブジェクトを解放するコードを開始します。このプリミティブは、ロック解除呼び出しの前に呼び出されます。 |
各 API は単一の引数、アドレス (addr) を持ちます。値ではなくアドレスを指定して、2 つ以上の異なるカスタム同期オブジェクトを区別します。インテル® VTune™ プロファイラーは、アドレスごとに個別のカスタム・オプションを追跡できます。そのため、同じカスタム・オブジェクトでコードの異なる領域へのアクセスを保護するには、それぞれで同じ addr パラメーターを使用します。
コードに正しく埋め込まれていると、プリミティブはコードが行おうとする同期タイプをインテル® VTune™ プロファイラーに通知します。各 prepare プリミティブは、cancel プリミティブまたは acquired プリミティブとペアにする必要があります。
ユーザー定義の同期構造には任意の数の同期オブジェクトを含めることができます。各同期オブジェクトは、ユーザー定義同期 API がオブジェクトの追跡に使用する固有のメモリーハンドルから起動する必要があります。オブジェクトが一意のメモリーポインターを使用する限り、ユーザー定義同期 API を使用して、同時に複数の同期オブジェクトを追跡できます。これは、Windows* API の WaitForMultipleObjects 関数に似たモデル・オブジェクトであると考えることができます。同期オブジェクトのグループから、さらに複雑な同期構造を作成できます。ただし。誤った動作の可能性があるため、異なるユーザー定義同期構造をインターレースすることは推奨できません。
ユーザー定義同期 API は、コード内にプリミティブを適切に配置する必要があります。ユーザー定義同期 API を適切に使用するには次のガイドラインに従ってください。
同期オブジェクトへのアクセスを取得するコードの直前に prepare プリミティブを配置します。
コードが同期オブジェクトを待機しなくなった直後に、cancel または acquired プリミティブを配置します。
releasing プリミティブは、コードが同期オブジェクトを保持しないことを通知する直前に使用する必要があります。
複数のオブジェクトを待機する構造をシミュレートするため複数の prepare プリミティブを使用する場合、prepare プリミティブのグループに関連するオブジェクトの最後の cancel または acquired プリミティブが、構造の動作が cancel または acquired であるかを決定します。
prepare プリミティブと acquired プリミティブ間の時間は、影響時間であると見なすことができます。
プロセッサーがブロックしなくても、prepare プリミティブと cancel プリミティブ間の時間はブロック時間と見なされます。
ユーザー定義同期 API の使い方を誤ると、統計データが不正確になります。
prepare API は、現在のスレッドがメモリー位置でシグナルの待機を開始することをインテル® VTune™ プロファイラーに通知します。この呼び出しは、ユーザー同期構造を呼び出す前に行わなければなりません。準備 API は、取得またはキャンセル API 呼び出しとペアである必要があります。
次のコード例は、ユーザー定義のスピン待機構造と組み合わせて使用される準備および取得 API の使い方を示します。
long spin = 1;
. . . . . . . .
__itt_sync_prepare((void *) &spin );
while(ResourceBusy); // スピンウェイト;
__itt_sync_acquired((void *) &spin );cancel API は、スレッドが同期構造をテストし、別のスレッドからのシグナルを待機する代わりに、ほかのワークを実行するシナリオに適用できます。次のコード例を参照してください。
long spin = 1;
. . . . . . . .
__itt_sync_prepare((void *) &spin ); while(ResourceBusy) {
__itt_sync_cancel((void *) &spin );
//
// 有用なワークを実行
// . . . . . . . . . .
//
// 有用な処理が完了すると、この構造体はロック変数をテストし、
// 再度取得しようとします。これを行う前に、
// prepare API を呼び出す必要があります。
//
__itt_sync_prepare((void *) &spin );
}
__itt_sync_acquired((void *) &spin);ロックを取得した後、スレッドがロックを解放する前に releasing API を呼び出す必要があります。次の例は、releasing API の使用方法を示します。
long spin = 1; . . . . . . . . __itt_sync_prepare((void *) &spin ); while(ResourceBusy); // スピンウェイト; __itt_sync_acquired((void *) &spin );
次のコード例は、ユーザー定義同期 API を使用して追跡可能なクリティカル・セクションを作成する方法を示します。
CSEnter() {
__itt_sync_prepare((void*) &cs);
while(LockIsUsed) {
if(LockIsFree) {
// 実際にロックを取得するためのコードをここに記述
__itt_sync_acquired((void*) &cs);
}
if(timeout) {
__itt_sync_cancel((void*) &cs );
}
}
}
CSLeave() { if(LockIsMine) {
__itt_sync_releasing((void*) &cs);
// 実際にロックを解除するコードはここに記述
}
}このクリティカル・セクションの例は、ユーザー定義の同期プリミティブを作成します。次のことに注意して例を参照してください。
それぞれの準備プリミティブは、取得プリミティブまたはキャンセルプリミティブとペアになっています。
prepare プリミティブは、ユーザーコードがユーザーロックを待機する直前に配置します。
acquired プリミティブは、ユーザーコードがユーザーロックを取得した直後に配置します。
releasing プリミティブは、ユーザーコードがユーザーロックを解放する直前に配置します。これにより、スレッドがロックを解放したことをインテル® VTune™ プロファイラーが認識する前に、ほかのスレッドが acquired プリミティブを呼び出さないようにします。
バリアなど上位レベルの構造も同期 API を使用して容易にモデル化できます。次のコード例は同期 API を使用して追跡されるバリア構造を作成する方法を示します。
Barrier() {
teamflag = false;
__itt_sync_releasing((void *) &counter);
InterlockedIncrement(&counter);
// OS とコンパイラーに適したアトミック・インクリメント・プリミティブを使用
if( counter == thread count ) {
__itt_sync_acquired((void *) &counter);
__itt_sync_releasing((void *) &teamflag);
teamflag = true; counter = 0;
} else {
__ itt_sync_prepare((void *) &teamflag);
// teamflag を待機
__ itt_sync_acquired((void *) &teamflag);
}
}次のことに注意して例を参照してください。
このバリアコードには 2 つの同期オブジェクトがあります。カウンターオブジェクトは、スレッドがバリアに入ったことを示すため、すべてのスレッドから最後のスレッドへギャザーのようなシグナルを通知する際に使用されます。最後のスレッドがバリアに到達すると、チームフラグオブジェクトを使用してすべてのスレッドに継続が可能であることを通知します。
各スレッドがバリアに入ると、counter をインクリメントして最後のスレッドに通知することをインテル® VTune™ プロファイラーに伝えるため __itt_sync_releasing を呼び出します
バリアに入る最後のスレッドは、__itt_sync_acquired を呼び出してその他のスレッドからのシグナルを正常に受け取ったことをインテル® VTune™ プロファイラーに伝えます。
バリアに入る最後のスレッドは、__itt_sync_releasing を呼び出して teamflag を設定することで、その他のスレッドでバリアが完了したことをインテル® VTune™ プロファイラーに伝えます。
最後のスレッドを除くほかのスレッドは、__itt_sync_prepare プリミティブを呼び出して、最後のスレッドからの teamflag シグナルを待機していることをインテル® VTune™ プロファイラーに伝えます。
最後に、バリアを出る前に、各スレッドは __itt_sync_acquired プリミティブを呼び出してバリア終了シグナルを正常に受信したことをインテル® VTune™ プロファイラーに伝えます。