Introduction to OpenMP (part 2)
Section construct
As we saw earlier, all threads within the team execute the entire structured block that follows a parallel construct. Only a very limited number of parallel algorithms can be implemented in this way. It is much more common that we have a set of mutually independent operations which we want to execute in parallel.
One way of accomplishing this is with the sections and section constructs:
#pragma omp sections [clause[ [,] clause] ... ] new-line
{
[#pragma omp section new-line]
structured-block
[#pragma omp section new-line
structured-block]
...
}
where clause is one of the following:
private(list)
firstprivate(list)
lastprivate([ lastprivate-modifier:] list)
reduction([reduction-modifier ,] reduction-identifier : list)
allocate([allocator :] list)
nowait
The structured blocks that follow the section
constructs inside the sections
construct are distributed among the threads within the team:
Each structured block is executed only once:
1#include <stdio.h>
2
3int main() {
4
5 #pragma omp parallel
6 {
7 printf("Everyone!\n");
8
9 #pragma omp sections
10 {
11 #pragma omp section
12 printf("Only me!\n");
13
14 #pragma omp section
15 printf("No one else!\n");
16
17 #pragma omp section
18 printf("Just me!\n");
19 }
20 }
21
22 return 0;
23}
$ gcc -o my_program my_program.c -Wall -fopenmp
$ ./my_program
Everyone!
Only me!
No one else!
Just me!
Everyone!
Everyone!
...
Note how the Everyone!
lines are printed multiple times but the other three lines are printed only once.
If we want, we can merge the parallel
and sections
constructs together:
1#include <stdio.h>
2
3int main() {
4
5 #pragma omp parallel sections
6 {
7 #pragma omp section
8 printf("Only me!\n");
9
10 #pragma omp section
11 printf("No one else!\n");
12
13 #pragma omp section
14 printf("Just me!\n");
15 }
16
17 return 0;
18}
$ gcc -o my_program my_program.c -Wall -fopenmp
$ ./my_program
Just me!
No one else!
Only me!
Parallel loop construct
Most programs contain several loops and parallelizing these loops is often a natural way to add some parallelism to a program.
The loop
construct does exactly that:
#pragma omp loop [clause[ [,] clause] ... ] new-line
for-loops
The construct tells OpenMP that the loop iterations are free of data dependencies and can therefore be executed in parallel.
The loop iterator is private
by default:
1#include <stdio.h>
2
3int main() {
4 #pragma omp parallel
5 {
6 #pragma omp loop
7 for (int i = 0; i < 5; i++)
8 printf("The loop iterator is %d.\n", i);
9 }
10}
$ gcc -o my_program my_program.c -Wall -fopenmp
$ ./my_program
The loop iterator is 1.
The loop iterator is 4.
The loop iterator is 0.
The loop iterator is 2.
The loop iterator is 3.
Like many other constructs, the loop
construct accepts several clauses:
bind(binding)
collapse(n)
order(concurrent)
private(list)
lastprivate(list)
reduction([default ,]reduction-identifier : list)
In particular, the collapse
clause allows us to collapse n
nested loops into a single parallel loop.
Otherwise, only the iterations of the outermost loop are executed in parallel.
If we want, we can merge the parallel
and loop
constructs together:
1#include <stdio.h>
2
3int main() {
4 #pragma omp parallel loop
5 for (int i = 0; i < 5; i++)
6 printf("The loop iterator is %d.\n", i);
7}
$ gcc -o my_program my_program.c -Wall -fopenmp
$ ./my_program
The loop iterator is 4.
The loop iterator is 0.
The loop iterator is 2.
The loop iterator is 3.
The loop iterator is 1.
Or use an older for
construct:
1#include <stdio.h>
2
3int main() {
4 #pragma omp parallel for
5 for (int i = 0; i < 5; i++)
6 printf("The loop iterator is %d.\n", i);
7}
$ gcc -o my_program my_program.c -Wall -fopenmp
$ ./my_program
The loop iterator is 3.
The loop iterator is 1.
The loop iterator is 0.
The loop iterator is 2.
The loop iterator is 4.
Single and master constructs
It is sometimes necessary to execute a structured block only once inside a parallel region.
The single
construct does exactly this:
#pragma omp single [clause[ [,] clause] ... ] new-line
structured-block
The structured block is executed only once by one of the threads in the team:
1#include <stdio.h>
2
3int main() {
4 #pragma omp parallel
5 {
6 printf("In parallel.\n");
7 #pragma omp single
8 printf("Only once.\n");
9 printf("More in parallel.\n");
10 }
11}
$ gcc -o my_program my_program.c -Wall -fopenmp
$ ./my_program
In parallel.
Only once.
In parallel.
In parallel.
...
In parallel.
More in parallel.
More in parallel.
...
More in parallel.
Note that all In parallel
lines and the Only once
line are printed before any More in parallel
lines are printed.
This happens because the single
construct introduces an implicit barrier to the exit of the single region.
That is, all threads in the team must wait until one of the threads has executed the structured block that is associated with the single
construct:
We can disable this behaviour using the nowait
clause:
private(list)
firstprivate(list)
copyprivate(list)
allocate([allocator :] list)
nowait
The single
construct is closely connected to the master
construct:
#pragma omp master new-line
structured-block
However, there are two primary differences:
Only the master thread of the current team can execute the associated structured block.
There is no implied barrier either on entry to, or exit from, the master region.
Critical construct
It is sometimes necessary to allow only one thread to execute a structured block concurrently:
#pragma omp critical [(name) [[,] hint(hint-expression)] ] new-line
structured-block
Several critical
constructs can be joined together by giving them the same name:
#pragma omp critical (protect_x)
x++;
...
#pragma omp critical (protect_x)
x = x - 15;
Barrier construct
Finally, we can add an explicit barrier:
#pragma omp barrier new-line
That is, all threads in the team must wait until all other threads in the team have encountered the barrier
construct: