OpenMP並列化の一般的なアルゴリズムの使用
22945 ワード
OpenMP並列化の一般的なアルゴリズム
Openmpは強力なパラレルコンパイルライブラリで、ダイナミックにマルチスレッドを設定でき、使いやすいです.ここでは検索アルゴリズムを例に、OpenMPで一般的なアルゴリズムを並列化する方法を紹介します.
旅行者問題
旅行業者の問題はもうありふれたことだ.有向帯域重みマップ内で、最短ループを探します(頂点ごとに1回のみ通過し、重み値の合計が最小です).実際,この問題はNPhard問題であり,解決するには指数級の時間的複雑さしかかからない.BFSとDFSを用いてそれを解くことを試みた.問題は各頂点を通過することを要求しているので、ループは現れません.つまり、私たちの検索がすでに通過した頂点を歩かないことを約束すれば、検索の状態は永遠に交差しません.つまり、私たちは解空間ツリーで解く必要があります.検索状態を表す構造体と、次の状態を求めるアルゴリズムを定義します.
successorsアルゴリズムは簡単で,構造体で頂点が遍歴されたか否かを判別する方法order[i]=−1を定義し,すべての頂点を遍歴し,遍歴されていない頂点を後継頂点として新しい解状態ノードを生成するだけでよい.DFSアルゴリズムを考慮すると,非再帰的な形式,すなわちスタックとサイクルシミュレーションで再帰すれば,自然にサイクル形式のDFSアルゴリズムを実現できる.解空間ツリーを検索している以上,ループの各部分に相関がないのでparallel for文でDFSを直接並列化できるのはかなり簡単である.解空間ツリーの分岐因子は最上位層でn−1であり,下位層毎にforループを単純に分割して並列を実現する場合,最上位層で分割するのが最適解である.nがスレッド数よりはるかに大きい場合は自然に可能であるが,本問題はNPhard問題であり,nが大きい場合は解くことが不可能であり,一般的にnは数十程度であるが,我々のスレッド数は多くなる可能性があり,これにより大きな負荷不均衡をもたらし,我々の従来の並列優位性を失う.このためには並列化されたBFSが必要であり,BFSはキューによって行われる.毎回チームの頭からノードを取り出し、successorsを求めて、新しいノードをチームの尾に入れます.各successors動作間は並列であり,一度のsuccessors動作では線形時間の複雑さしか必要とせず,従来の大規模な問題を粒子化することが容易に分かった.我々のマルチスレッドが毎回キューから要素を要求し,successorsを並列に実行し,新しいノードをキューに入れる限り,良好な負荷等化並列探索アルゴリズムを実現した.注意すべき点は、キューは並列popとpushを許可しないため、操作キューのコードはスレッドロックを設定されるべきである.また,グローバル変数を修正しsolutionを最適に解く場合にもスレッドロックを設計する.もう一つの問題は,単一スレッドでは,キューが空の場合が検索完了の場合であると直接考えている.ただし、マルチスレッドでは、検索が完了していない場合でも、キューが空になる可能性があります.この場合、スレッドがキュー内の要求要素に行くと、エラーが発生します.したがって,検索を完了するには他の論理が必要であり,すべてのリーフノード,すなわちgoalstateが検索されると,検索は本当に終了する.これまで、キューから要素を取得できなくてもループを終了することはできず、待機する必要があるスレッドもありました.bfsのcppコードは以下の通りです.
Openmpは強力なパラレルコンパイルライブラリで、ダイナミックにマルチスレッドを設定でき、使いやすいです.ここでは検索アルゴリズムを例に、OpenMPで一般的なアルゴリズムを並列化する方法を紹介します.
旅行者問題
旅行業者の問題はもうありふれたことだ.有向帯域重みマップ内で、最短ループを探します(頂点ごとに1回のみ通過し、重み値の合計が最小です).実際,この問題はNPhard問題であり,解決するには指数級の時間的複雑さしかかからない.BFSとDFSを用いてそれを解くことを試みた.問題は各頂点を通過することを要求しているので、ループは現れません.つまり、私たちの検索がすでに通過した頂点を歩かないことを約束すれば、検索の状態は永遠に交差しません.つまり、私たちは解空間ツリーで解く必要があります.検索状態を表す構造体と、次の状態を求めるアルゴリズムを定義します.
/*
, order[i] i
, order[i] 0, 1
front , 。
order[i]==-1 , 。
*/
struct Node
{
int order[N];
int front = -1;
int cost = 0;
Node() { fill(order, order + N, -1); }
};
successorsアルゴリズムは簡単で,構造体で頂点が遍歴されたか否かを判別する方法order[i]=−1を定義し,すべての頂点を遍歴し,遍歴されていない頂点を後継頂点として新しい解状態ノードを生成するだけでよい.DFSアルゴリズムを考慮すると,非再帰的な形式,すなわちスタックとサイクルシミュレーションで再帰すれば,自然にサイクル形式のDFSアルゴリズムを実現できる.解空間ツリーを検索している以上,ループの各部分に相関がないのでparallel for文でDFSを直接並列化できるのはかなり簡単である.解空間ツリーの分岐因子は最上位層でn−1であり,下位層毎にforループを単純に分割して並列を実現する場合,最上位層で分割するのが最適解である.nがスレッド数よりはるかに大きい場合は自然に可能であるが,本問題はNPhard問題であり,nが大きい場合は解くことが不可能であり,一般的にnは数十程度であるが,我々のスレッド数は多くなる可能性があり,これにより大きな負荷不均衡をもたらし,我々の従来の並列優位性を失う.このためには並列化されたBFSが必要であり,BFSはキューによって行われる.毎回チームの頭からノードを取り出し、successorsを求めて、新しいノードをチームの尾に入れます.各successors動作間は並列であり,一度のsuccessors動作では線形時間の複雑さしか必要とせず,従来の大規模な問題を粒子化することが容易に分かった.我々のマルチスレッドが毎回キューから要素を要求し,successorsを並列に実行し,新しいノードをキューに入れる限り,良好な負荷等化並列探索アルゴリズムを実現した.注意すべき点は、キューは並列popとpushを許可しないため、操作キューのコードはスレッドロックを設定されるべきである.また,グローバル変数を修正しsolutionを最適に解く場合にもスレッドロックを設計する.もう一つの問題は,単一スレッドでは,キューが空の場合が検索完了の場合であると直接考えている.ただし、マルチスレッドでは、検索が完了していない場合でも、キューが空になる可能性があります.この場合、スレッドがキュー内の要求要素に行くと、エラーが発生します.したがって,検索を完了するには他の論理が必要であり,すべてのリーフノード,すなわちgoalstateが検索されると,検索は本当に終了する.これまで、キューから要素を取得できなくてもループを終了することはできず、待機する必要があるスレッドもありました.bfsのcppコードは以下の通りです.
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define N 4
/*
:
。
n ,
, “ ”,
cost 。 ,
,
。
: ,
DFS 。 , “
”, n , m ,
( ) (n-1)! 。 n-1,
, for ,
。 comm_sz n ,
, ,
BFS , , , successors
。 O(n), m=b,
。 “ ” ,
。 , queue,
queue , 。
( ), cost 。
, n , n ,
。
*/
struct Node
/*
, order[i] i
, order[i] 0, 1
front , 。
order[i]==-1 , 。
*/
{
int order[N];
int front = -1;
int cost = 0;
Node() { fill(order, order + N, -1); }
};
queue<Node> Q;//
int bestPath[N];//
int bestCost = 9999999;
int graph[N][N];//
void showpath(int order[N]) {
int path[N];
for (int i = 0;i < N;i++) {
path[order[i]] = i;
}
for (int i = 0;i < N;i++) {
cout << path[i] << "->";
}
cout << 0 << endl;
}
/*
input.txt:
0 1 3 8
5 0 2 6
1 18 0 10
7 4 12 0
*/
int main()
{
freopen("input.txt", "r", stdin);
for (int i = 0;i < N;i++)
for (int j = 0;j < N;j++)
cin >> graph[i][j];
Node start;
start.order[0] = 0;
start.front = 0;// 0
Q.push(start);
omp_lock_t changeQ, changeSolu;
omp_init_lock(&changeQ);
omp_init_lock(&changeSolu);
int goal_cnt = 0;
/* goal
,
, , ,
。
, , goalstate
, 。 ,
, , */
int MAX_CNT = 1;
for (int i = 1;i < N;i++) MAX_CNT *= i;
# pragma omp parallel num_threads(4)
{
while (goal_cnt<MAX_CNT)
{
Node n;
omp_set_lock(&changeQ);
if (!Q.empty()) {
n = Q.front();
Q.pop();
}
omp_unset_lock(&changeQ);
if (n.front==-1) //
continue;
bool goal = 1;
for (int i = 0;i < N;i++) {
if (n.order[i] != -1)
continue;
else {
Node newNode;
copy(n.order, n.order + N, newNode.order);
newNode.order[i] = n.order[n.front] + 1;
newNode.cost = n.cost + graph[n.front][i];
newNode.front = i;
omp_set_lock(&changeQ);
cout << "Process " << omp_get_thread_num() << " push" << endl;
for (int x : newNode.order)
cout << x << " ";
cout << endl;
Q.push(newNode);
omp_unset_lock(&changeQ);
goal = 0;
}
}
if (goal) {//
omp_set_lock(&changeSolu);
int final_cost = n.cost + graph[n.front][0];
goal_cnt++;
if (final_cost < bestCost) {
bestCost = final_cost;
copy(n.order, n.order + N, bestPath);
}
omp_unset_lock(&changeSolu);
}
}
}
cout << "Best path is: ";
showpath(bestPath);
cout << "Cost: " << bestCost << endl;
}