Search code examples
openmpdependency-graph

Produce OpenMP code given dependency graph


I have a question on how to produce OpenMP pseudocode when you have a specific dependency graph in mind. So suppose that we have this specific graph:

Dependence graph

A solution could be something like this:

    #pragma omp parallel
    {
        #pragma omp single
        {
            A();
            #pragma omp task B();
            #pragma omp task C();
            D();
            #pragma omp taskwait
            #pragma omp task E();
            F();
        }
    }

Now the thing is that although the code above does succeed important parallelism, task E has to wait for task D to complete and task F has to wait for task B to complete, which is not required according to the graph.

So my question is, can someone provide me with OpenMP pseudocode where E won't wait for D and F won't wait for B for the given dependency graph?


Solution

  • For this purpose, the OpenMP standard proposes the depend clause for the task directive.

    In your specific case, I guess this could be used like this:

    #include <stdio.h>
    
    int a, b, c;
    
    void A() {
        a = b = c = 1;
        printf( "[%d]In A: a=%d b=%d c=%d\n", omp_get_thread_num(), a, b, c );
    }
    
    void B() {
        a++;
        sleep( 3 );
        printf( "[%d]In B: a=%d\n", omp_get_thread_num(), a );
    }
    
    void C() {
        b++;
        sleep( 2 );
        printf( "[%d]In C: b=%d\n", omp_get_thread_num(), b );
    }
    
    void D() {
        c++;
        sleep( 1 );
        printf( "[%d]In D: c=%d\n", omp_get_thread_num(), c );
    }
    
    void E() {
        a++;
        sleep( 3 );
        printf( "[%d]In E: a=%d, b=%d\n", omp_get_thread_num(), a, b );
    }
    
    void F() {
        c++;
        sleep( 1 );
        printf( "[%d]In F: b=%d c=%d\n", omp_get_thread_num(), b, c );
    }
    
    int main() {
    
        #pragma omp parallel num_threads( 8 )
        {
            #pragma omp single
            {
               #pragma omp task depend( out: a, b, c )
               A();
               #pragma omp task depend( inout: a )
               B();
               #pragma omp task depend( inout: b )
               C();
               #pragma omp task depend( inout: c )
               D();
               #pragma omp task depend( inout: a ) depend( in: b )
               E();
               #pragma omp task depend( inout: c ) depend( in: b )
               F();
            }
        }
        printf( "Finally a=%d b=%d c=%d\n", a, b, c );
    
        return 0;
    }
    

    As you can see, I introduced some variables a, b and c which I use to define dependencies across tasks. I also modify them in the call accordingly, although this isn't necessary (I only did it for showing how the flow was handled).

    And here is what I get on my machine:

    ~/tmp$ gcc -fopenmp depend.c
    ~/tmp$ ./a.out 
    [6]In A: a=1 b=1 c=1
    [7]In D: c=2
    [2]In C: b=2
    [6]In B: a=2
    [2]In F: b=2 c=3
    [6]In E: a=3, b=2
    Finally a=3 b=2 c=3