インテル® 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™ プロファイラーに通知します。この呼び出しは、ユーザー同期構造を呼び出す前に行わなければなりません。prepare API は、acquired または cancel API 呼び出しとペアにする必要があります。
次のコード例は、ユーザー定義のスピン待機構造と組み合わせて使用される prepare および acquired 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_releasing((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 プリミティブまたは cancel プリミティブとペアにします。
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);
// チームフラグを待機します
__ itt_sync_acquired((void *) &teamflag);
}
}
次のことに注意して例を参照してください。
このバリアコードには 2 つの同期オブジェクトがあります。counter オブジェクトは、スレッドがバリアに入ったことを示すため、すべてのスレッドから最後のスレッドへギャザーのようなシグナル通知を行う際に使用されます。最後のスレッドはバリアに到達すると、teamflag オブジェクトを使用してすべてのスレッドに継続が可能であることを通知します。
各スレッドはバリアに入ると、counter をインクリメントして最後のスレッドに通知することをインテル® VTune™ プロファイラーに伝えるため __itt_sync_releasing を呼び出します
バリアに入る最後のスレッドは、__itt_sync_acquired を呼び出してその他のスレッドからのシグナルを正常に受け取ったことをインテル® VTune™ プロファイラーに伝えます。
バリアに入る最後のスレッドは、teamflag を設定してその他のスレッドにバリアの完了を通知することをインテル® VTune™ プロファイラーに伝えるため __itt_sync_releasing を呼び出します。
最後のスレッドを除くほかのスレッドは、__itt_sync_prepare プリミティブを呼び出して、最後のスレッドからの teamflag シグナルの待機を開始することをインテル® VTune™ プロファイラーに伝えます。
最後に、バリアを出る前に、各スレッドは __itt_sync_acquired プリミティブを呼び出してバリア終了シグナルを正常に受信したことをインテル® VTune™ プロファイラーに伝えます。