[英]How can I parallelize DFS using OpenMP?
我正试图用 OpenMP 解决这个问题。 我需要并行化深度优先遍历。
这是算法:
void dfs(int v){
visited[v] = true;
for (int i = 0; i < g[v].size(); ++i) {
if (!visited[g[v][i]]) {
dfs(g[v][i]);
}
}
}
我尝试:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <queue>
#include <sstream>
#include <omp.h>
#include <fstream>
#include <vector>
using namespace std;
vector<int> output;
vector<bool> visited;
vector < vector <int> >g;
int global = 0;
void dfs(int v)
{
printf(" potoki %i",omp_get_thread_num());
//cout<<endl;
visited[v] = true;
/*for(int i =0;i<visited.size();i++){
cout <<visited[i]<< " ";
}*/
//cout<<endl;
//global++;
output.push_back(v);
int i;
//printf(" potoki %i",omp_get_num_threads());
//cout<<endl;
for (i = 0; i < g[v].size(); ++i) {
if (!visited[g[v][i]]) {
#pragma omp task shared(visited)
{
#pragma omp critical
{
dfs(g[v][i]);
}
}
}
}
}
main(){
omp_set_num_threads(5);
int length = 1000;
int e = 4;
for (int i = 0; i < length; i++) {
visited.push_back(false);
}
int limit = (length / 2) - 1;
g.resize(length);
for (int x = 0; x < g.size(); x++) {
int p=0;
while(p<e){
int new_e = rand() % length ;
if(new_e!=x){
bool check=false;
for(int c=0;c<g[x].size();c++){
if(g[x][c]==new_e){
check=true;
}
}
if(check==false){
g[x].push_back(new_e);
p++;
}
}
}
}
ofstream fin("input.txt");
for (int i = 0; i < g.size(); i++)
{
for (int j = 0; j < g[i].size(); j++)
{
fin << g[i][j] << " ";
}
fin << endl;
}
fin.close();
/*for (int x = 0; x < g.size(); x++) {
for(int j=0;j<g[x].size();j++){
printf(" %i ", g[x][j]);
}
printf(" \n ");
}*/
double start;
double end;
start = omp_get_wtime();
#pragma omp parallel
{
#pragma omp single
{
dfs(0);
}
}
end = omp_get_wtime();
cout << endl;
printf("Work took %f seconds\n", end - start);
cout<<global;
ofstream fout("output.txt");
for(int i=0;i<output.size();i++){
fout<<output[i]<<" ";
}
fout.close();
}
图“g”生成并写入文件 input.txt。 程序的结果写入文件 output.txt。
但这不适用于任意数量的线程,而且速度要慢得多。 我尝试使用taskwait
,但在那种情况下,只有一个线程工作。
临界区保护一个代码块,以便在任何给定时间只有一个线程可以执行它。 在临界区中递归调用dfs()
意味着没有两个任务可以同时进行该调用。 此外,由于dfs()
是递归的,任何顶级任务都必须等待整个递归完成才能退出临界区并允许另一个线程中的任务执行。
您需要在不会干扰递归调用的地方进行同步,并且只保护不提供自身内部同步的共享数据的更新。 这是原始代码:
void dfs(int v){
visited[v] = true;
for (int i = 0; i < g[v].size(); ++i) {
if (!visited[g[v][i]]) {
dfs(g[v][i]);
}
}
}
一个天真的但仍然并行的版本是:
void dfs(int v){
#pragma omp critical
{
visited[v] = true;
for (int i = 0; i < g[v].size(); ++i) {
if (!visited[g[v][i]]) {
#pragma omp task
dfs(g[v][i]);
}
}
}
}
在这里,一旦创建了任务,代码就会离开临界区。 这里的问题是dfs()
的整个 body 是一个临界区,这意味着即使有 1000 次并行的递归调用,它们也会依次执行,而不是并行执行。 由于持续的缓存失效和增加的 OpenMP 开销,它甚至会比顺序版本慢。
一个重要的注意事项是OpenMP 临界区,就像常规的 OpenMP 锁一样,不是可重入的,因此线程很容易由于在来自同一临界区内部的递归调用中遇到相同的临界区而使自身死锁,例如,如果任务立即执行而不是被推迟。 因此,最好使用 OpenMP 嵌套锁实现可重入临界区。
该代码比顺序慢的原因是它除了遍历图形之外什么都不做。 如果它在每个节点上做一些额外的工作,例如,访问数据或计算节点本地属性,那么可以在更新已visited
和未访问邻居的循环之间插入这项工作:
void dfs(int v){
#pragma omp critical
visited[v] = true;
// DO SOME WORK
#pragma omp critical
{
for (int i = 0; i < g[v].size(); ++i) {
if (!visited[g[v][i]]) {
#pragma omp task
dfs(g[v][i]);
}
}
}
}
临界区中的部分仍将按顺序执行,但// DO SOME WORK
表示的处理将并行重叠。
有一些技巧可以通过减少由一个大锁/关键部分引入的锁争用来加快速度。 例如,可以使用一组 OpenMP 锁并将visited
的索引映射到这些锁上,例如,使用此处描述的简单模运算。 也可以在某个递归级别停止创建任务,而是调用dfs()
的顺序版本。
void p_dfs(int v)
{
#pragma omp critical
visited[v] = true;
#pragma omp parallel for
for (int i = 0; i < graph[v].size(); ++i)
{
#pragma omp critical
if (!visited[graph[v][i]])
{
#pragma omp task
p_dfs(graph[v][i]);
}
}
}
OpenMP 适用于数据并行代码,前提是工作量是预先知道的。 对于像这样的图算法来说效果不佳。
如果您唯一要做的就是代码中的内容(将元素推入向量中),并行性会使其变慢。 即使您的图表上有许多千兆字节的数据,瓶颈是内存不是计算,多个 CPU 内核也无济于事。 此外,如果所有线程都将结果推送到同一个向量,则需要同步。 此外,在现代处理器上,读取最近由另一个 CPU 内核写入的内存成本很高,甚至比缓存未命中还要昂贵。
如果您除了复制整数之外还有一些 CPU 密集型的工作,请寻找 OpenMP 的替代品。 在 Windows 上,我通常使用CreateThreadpoolWork
和SubmitThreadpoolWork
API。 在 iOS 和 OSX 上,请参阅大中央调度。 在 Linux 上,请参阅 cp_thread_pool_create(3) 但与其他两个不同,我没有任何实际操作经验,只是找到了文档。
无论您要使用哪种线程池实现,您都可以在遍历图时动态地将工作发布到线程池。 OpenMP 在底层也有一个线程池,但 API 不够灵活,无法实现动态并行。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.