【问题标题】:How can I parallelize DFS using OpenMP?如何使用 OpenMP 并行化 DFS?
【发布时间】:2020-11-16 14:03:08
【问题描述】:

我正在尝试使用 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,但在这种情况下,只有一个线程有效。

【问题讨论】:

标签: c++ parallel-processing openmp depth-first-search


【解决方案1】:

临界区保护代码块,因此在任何给定时间都不能有超过一个线程可以执行它。在临界区内递归调用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 的索引映射到这些锁上,例如,使用here 所述的简单模运算。也可以在某个递归级别停止创建任务,并改为调用dfs() 的顺序版本。

【讨论】:

    【解决方案2】:
    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]);
                }
            }
    
    }
    

    【讨论】:

      【解决方案3】:

      当工作量预先知道时,OpenMP 适用于数据并行代码。不适用于像这样的图形算法。

      如果您唯一要做的就是代码中的内容(将元素推入向量中),那么并行性会使其变慢。即使你的图表上有很多千兆字节的数据,瓶颈是内存而不是计算,多个 CPU 内核也无济于事。此外,如果所有线程都将结果推送到同一个向量,则需要同步。此外,在现代处理器上读取最近由另一个 CPU 内核写入的内存成本很高,甚至比缓存未命中还要高。

      如果除了复制整数之外,您还有一些需要大量 CPU 的工作,请寻找 OpenMP 的替代方案。在 Windows 上,我通常使用 CreateThreadpoolWorkSubmitThreadpoolWork API。在 iOS 和 OSX 上,请参阅 Grand Central Dispatch。在 Linux 上,请参阅 cp_thread_pool_create(3),但与其他两个不同的是,我没有任何实践经验,只是找到了文档。

      无论您要使用哪种线程池实现,您都可以在遍历图时动态地将工作发布到线程池。 OpenMP 在底层也有一个线程池,但该 API 对于动态并行来说不够灵活。

      【讨论】:

      • OpenMP 自 3.0 (2008) 版本以来就具有基于任务的并行性,非常适合动态工作负载,OP 使用它来完成他的任务。关键部分出现在错误的位置会减慢速度。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-03-09
      • 2023-04-11
      • 2013-09-19
      • 1970-01-01
      • 2015-08-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多