プライベート・メモリー位置の作成

重要なことは、タスクを実行するごとに、元のプログラムの共有メモリー位置を取得する代わりに、固有のプライベート・メモリー位置を取得する必要があることです。これには以下が含まれます。

使用している共有メモリー位置に応じて、対応方法は異なります。

ローカル変数の置き換え  

共有メモリー位置が、タスクの静的範囲を含む関数のローカル変数であるなら、対応は簡単です。

  1. 必要に応じて、静的範囲を中かっこで囲みブロックを明確にします。

  2. ブロックの先頭で新しい変数を定義します。

  3. 静的範囲内のすべての共有変数を新しい変数に置き換えます。

これで、タスクごとに固有のローカル変数のコピーを利用できます。

静的/グローバル変数、またはクラスの静的データメンバーの置き換え

大規模ソフトウェアの設計において、グローバル変数を使用するのは良い考えとは言えません。グローバル変数は設計の問題を容易に解決する方法に思われますが、プログラム内部で不明瞭な依存関係を引き起こし、プログラムの理解と変更を困難にします。プログラムを並列実行するように変更する場合、これらの問題は複雑化し、グローバル変数はさまざまなデータ共有問題の原因となります。

したがって、グローバル変数を排除する変更は、共有問題を解決するだけでなく、プログラムが正しく並列動作することを可能にします。これは、プログラムを並列化しない場合であっても、プログラムの理解と保守を容易にします。

以下に例を示します。

extern int global;
// ...
ANNOTATE_SITE_BEGIN(site1); 
     ANNOTATE_TASK_BEGIN(taskname); 
     foo(i); 
     bar(i); 
     ANNOTATE_TASK_END(); 
// ...
ANNOTATE_SITE_END(site1); v
oid foo(int i) { 
     global = x*3 - a[i]; 
} 
void bar(int i) { 
     b[i] = b[i] - global; 
}

静的/グローバル変数やクラスの静的データメンバーをプライベートとして作成する手法は、ローカル変数を使用する場合と同じです。重要な違いは、グローバル変数はタスクの動的な範囲内のほかの関数でアクセスされる可能性があるため、対応するプライベート変数はそれらの関数からアクセス可能にする必要があります。

  1. ローカル変数を置き換える場合と同様に、静的範囲でローカル変数を定義して共有変数の代わりに使用します。

  2. 静的範囲内のすべての共有変数を新しいローカル変数に置き換えます。

  3. タスクがほかの関数を呼び出す場合、それぞれに追加の参照引数を追加して、プライベート引数を渡します。C 言語でプログラミングを行う場合、ポインター引数を使用して変数のアドレスを渡す必要があります。

  4. 呼び出された関数内のすべての共有変数を引数参照に置き換えます。

これらの規則を上記の例に適用すると、次のようになります。

// ...
ANNOTATE_SITE_BEGIN(site1);
ANNOTATE_TASK_BEGIN(taskname);
int replacement;
foo(i, replacement);
bar(i, replacement);
ANNOTATE_TASK_END();
ANNOTATE_SITE_END(site1);
// ...
void foo(int i, int& replacement)
{
    replacement = x*3 - a[i];
}
void bar(int i, int& replacement)
{
    b[i] = b[i] - replacement;
}

混在した呼び出し関数: タスクの動的範囲とタスク外部の両方から呼び出される関数がある場合、ステップ 3 と 4 でタスク内の共有問題は解決できますが、タスク外部の呼び出しには対応できません。この場合、2 つのソリューションがあります。

アドレスが渡される変数: ポインターを介してアクセスされる変数の特殊な問題は、ヘルプの「ポインターの逆参照」で説明しています。

複数の共有変数: 前述した方法は単純ですが、タスクにいくつかの偶発的共有変数がある場合、それらの調整は困難です。明確な解決策は、それぞれの偶発的共有変数に対応するフィールドを持つローカルな構造体変数として定義することです。タスクの動的範囲内の関数と呼び出しを変更して、構造体を引数として渡し、共有変数を構造体の適切なフィールドに置き換えます。

タスククラスを作成: タスクの動的範囲内の関数が関連している場合、関数をメンバー関数として、そして置換共有変数をデータメンバーとして持つ、新しいクラスメンバーを作成できます。そうすることで、追加の参照引数の代わりにクラスの this ポインターを使用できます。同じ例を使って考えてみます。

class TaskClass { 
public: 
ANNOTATE_SITE_BEGIN(site1);
    void the_task()
    {
        ANNOTATE_TASK_BEGIN(taskname);
        foo(i);
        bar(i);
        ANNOTATE_TASK_END();
    }
ANNOTATE_SITE_END(site1);
private: 
    int replacement;
    void foo(int i)
    {
        replacement = x*3 - a[i];
    }
    void bar(int i)
    {
        b[i] = b[i] - replacement;
    }
};

構造体フィールドの置き換え

場合によっては、オブジェクトの 1 つ以上のフィールドで共有の問題が発生する可能性があります。

struct Point { float x, y, z; };
extern Point p;
// ...
ANNOTATE_SITE_BEGIN(site1);
    ANNOTATE_TASK_BEGIN(taskname);
    p.x = a[i].x * scale_x;
    p.y = a[i].y * scale_y;
    foo(i);
    ANNOTATE_TASK_END();
ANNOTATE_SITE_END(site1);
// ...
void foo(int i)
{
    b[i].x = b[i].x - p.x;
    b[i].y = b[i].y - p.y;
}

最も簡潔な解決策は、共有フィールドに新しいローカル変数を導入することです。

struct Point { float x, y; };
extern Point p;
// ...
ANNOTATE_SITE_BEGIN(site1);
    ANNOTATE_TASK_BEGIN(taskname);
    float sub_p_x = a[i].x * scale_x;
    float sub_p_y = a[i].y * scale_y;
    foo(I, sub_p_x, sub_p_y);
    ANNOTATE_TASK_END();
ANNOTATE_SITE_END(site1);
// ...
void foo(int i, float& sub_p_x, float& sub_p_y)
{
    b[i].x = b[i].x - sub_p_x;
    b[i].y = b[i].y - sub_p_y;
}

オブジェクトのすべてのフィールドの共有が偶発的である場合、共有フィールドごとに個別の置換変数を作成するよりも、構造体全体の単一のローカル置換変数を作成する方が容易です。

struct Point { float x, y; };
extern Point p;
//...
ANNOTATE_SITE_BEGIN(site1);
    ANNOTATE_TASK_BEGIN(taskname);
    Point sub_p;
    sub_p.x = a[i].x * scale_x;
    sub_p.y = a[i].y * scale_y;
    foo(I, sub_p);
    ANNOTATE_TASK_END();
ANNOTATE_SITE_END(site1);
// ...
void foo(int i, Point& sub_p)
{
    b[i].x = b[i].x - sub_p.x;
    b[i].y = b[i].y - sub_p.y;
}

関連情報