Sun Studio 12: パフォーマンスアナライザ

ユーザーモード呼び出しスタック

このモデルを理解するもっとも簡単な方法は、OpenMP プログラムの呼び出しスタックを、そのプログラムの実行のさまざまな時点で見ることです。この節では、1 つのサブルーチン foo を呼び出す main プログラムを持つ単純なプログラムについて考えます。そのサブルーチンには単一の並列ループがあり、その中でいくつかのスレッドが作業を行い、ロックを求めて競合し、ロックの取得と解放を行い、critical セクションへの出入りを行います。追加の呼び出しスタックセットが示され、そこに 1 つのスレーブスレッドが別の関数 bar を呼び出したときの状態が反映されます。bar 関数は入れ子になった並列領域に入ります。

この表示では、並列領域内で費やされたすべての包括的時間が、抽出元となった関数内の包括的時間に含まれ、それには、OpenMP ランタイムで費やされた時間も含まれており、その包括的時間は main および _start まで伝搬されます。

このモデルの動作を表す呼び出しスタックは、このあとのサブセクションに示すような外観となります。並列領域関数の実際の名前は、前に説明したように、次の形式をとります。

foo -- 行 9 からの OMP 並列領域 [ [_$p1C9.foo]
bar -- 行 5 からの OMP 並列領域 [ [_$p1C5.bar]

わかりやすくするために、説明の中では次のように短縮した形式が使用されています。

foo -- OMP...
bar -- OMP...

説明の中で、プログラム実行中の、ある瞬間におけるすべてのスレッドからの呼び出しスタックが示されています。各スレッドの呼び出しスタックは、フレームのスタックとして示されており、個々のプロファイルイベントを単一スレッドに関するアナライザの「タイムライン」タブで選択した際のデータに一致し、リーフ PC が一番上になっています。「タイムライン」タブでは、各フレームの PC オフセットが表示されますが、ここでは省略されています。すべてのスレッドからのスタックが、水平方向の配列で示されています。アナライザの「タイムライン」タブでは、ほかのスレッドのスタックが、垂直方向に積まれたプロファイルバー内に表示されます。さらに、示されている表現の中では、すべてのスレッドのスタックが正確に同じ瞬間に取得されたかのように示されていますが、実際の実験では、各スタックはそれぞれのスレッド内で独立して取得され、互いに相対的なずれが存在する場合があります。

示されている呼び出しスタックは、アナライザまたは er_print ユーティリティーにおける「ユーザー」の表示モードで提示されるとおりのデータを表しています。

  1. 最初の並列領域の前

    最初の並列領域に入る前の時点で存在するスレッドは、マスタースレッドただ 1 つだけです。

    マスター

    foo

    main

    _start

  2. 最初の並列領域に入った時点

    この時点では、ライブラリがスレーブスレッドの作成を完了し、すべてのスレッドは、マスターもスレーブもそれぞれの作業チャンクの処理を開始しようとしています。すべてのスレッドは、並列領域用のコード foo-OMP... を その構造の OpenMP 指令が現れる foo から、または自動並列化されたループ文を含んでいる行から呼び出したものとして示されます。各スレッド内の並列領域用のコードは、並列領域内の最初の命令から <OMP オーバーヘッド> 関数として示されている OpenMP サポートライブラリを呼び出しています。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

    <OMP オーバーヘッド>

    <OMP オーバーヘッド>

    <OMP オーバーヘッド>

    <OMP オーバーヘッド>

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo

    foo

    foo

    foo

    main

    main

    main

    main

    _start

    _start

    _start

    _start

    <OMP オーバーヘッド> が現れる枠は小さいので、この関数は特定の実験に現れない場合があります。

  3. 並列領域の内部で実行中

    4 つのスレッドすべてが、並列領域内で有益な作業を実行しています。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo

    foo

    foo

    foo

    main

    main

    main

    main

    _start

    _start

    _start

    _start

  4. 並列領域内で作業チャンク間を実行中

    4 つのスレッドすべてが有益な作業を行なっていますが、1 つのスレッドが 1 つの作業チャンクを終了し、次のチャンクを取得しようとしています。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

     

    <OMP オーバーヘッド>

       

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo

    foo

    foo

    foo

    main

    main

    main

    main

    _start

    _start

    _start

    _start

  5. 並列領域内の critical セクションを実行中

    4 つのスレッドすべてが、それぞれ並列領域内で実行中です。スレッドの 1 つが critical セクション内にある一方、別の 1 つは critical セクションに到達する前 (またはそれを終了したあと) に実行中です。残りの 2 つは、critical セクションに入るのを待っています。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

    <OMP critical セクション待ち>

       

    <OMP critical セクション待ち>

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo

    foo

    foo

    foo

    main

    main

    main

    main

    _start

    _start

    _start

    _start

    収集されたデータからは、critical セクション内で実行中のスレッドの呼び出しスタックと、まだ到達していないスレッド、またはすでに critical セクションを通過したスレッドの呼び出しスタックを区別できません。

  6. 並列領域内でロックを迂回して実行中

    ロックを迂回するコードのセクションは、critical セクションとほとんど同じです。4 つのスレッドすべてが並列領域内で実行中です。1 つのスレッドがロックを保持しながら実行中で、1 つはロックを取得する前 (または取得して解放したあと) に実行中で、それ以外の 2 つのスレッドはロックを待っています。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

    <OMP ロック待ち>

       

    <OMP ロック待ち>

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo

    foo

    foo

    foo

    main

    main

    main

    main

    _start

    _start

    _start

    _start

    critical セクションの例のように、収集されたデータからは、ロックを保持して実行中のスレッドの呼び出しスタックと、ロックを取得する前または解放したあとに実行中のスタックを区別できません。

  7. 並列領域の終わり近く

    この時点では、3 つのスレッドがすべてその作業チャンクを終了しましたが、1 つのスレッドがまだ作業中です。この例の OpenMP 構造では、暗黙にバリアが指定されました。バリアがユーザーコードによって明示的に指定されていたとすると、<OMP 暗黙バリア> 関数は <OMP 明示バリア> によって置き換えられます。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

    <OMP 暗黙バリア>

    <OMP 暗黙バリア>

     

    <OMP 暗黙バリア>

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo

    foo

    foo

    foo

    main

    main

    main

    main

    _start

    _start

    _start

    _start

  8. 並列領域の終わり近くで、1 つ以上の縮約変数がある

    この時点では、2 つのスレッドがそのすべての作業チャンクを終了し、縮約計算を実行中ですが、1 つのスレッドはまだ作業中であり、4 番目のスレッドはすでに縮約の一部を終了し、バリアの位置で待機中です。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

    <OMP 縮約>

    <OMP 暗黙バリア>

     

    <OMP 暗黙バリア>

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo

    foo

    foo

    foo

    main

    main

    main

    main

    _start

    _start

    _start

    _start

    1 つのスレッドが <OMP 縮約> 関数内として示されていますが、縮約を行うために費やされる実際の時間はかなり短いのが普通で、呼び出しスタックの標本内に取得されることは、ほとんどありません。

  9. 並列領域の終わり

    この時点では、すべてのスレッドが並列領域内でのすべての作業チャンクを完了し、バリアに到達しています。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

    <OMP 暗黙バリア>

    <OMP 暗黙バリア>

    <OMP 暗黙バリア>

    <OMP 暗黙バリア>

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo

    foo

    foo

    foo

    main

    main

    main

    main

    _start

    _start

    _start

    _start

    すべてのスレッドがすでにバリアに到達しているので、それらはすべて先へ進むことができます。実験ですべてのスレッドがこの状態にあるのが見られることは、ほとんどありません。

  10. 並列領域を出たあと

    この時点では、すべてのスレーブスレッドが次の並列領域への進入を待っており、ユーザーが設定した各種の環境変数に応じて、スピン中またはスリープ中になります。プログラムは、逐次実行中です。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

    foo

         

    main

         

    _start

    <OMP アイドル>

    <OMP アイドル>

    <OMP アイドル>

  11. 入れ子の並列領域を実行中

    4 つのスレッドすべてが、それぞれ並列領域内で作業中です。1 つのスレーブスレッドが別の関数 bar を呼び出し、それによって入れ子の並列領域が作成され、それを処理するために追加のスレーブスレッドが作成されます。

    マスター 

    スレーブ 1 

    スレーブ 2 

    スレーブ 3 

    スレーブ 4 

     

    bar-OMP...

       

    bar-OMP...

     

    bar

       

    bar

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo-OMP...

    foo

    foo

    foo

    foo

    foo

    main

    main

    main

    main

    main

    _start

    _start

    _start

    _start

    _start