Paralelismo OpenMP irregular
Uma armadilha comum é acreditar que todos os encadeamentos de uma região paralela devem instanciar (criar) tarefas, mas esse normalmente não é o caso, a menos que você queira criar tantas tarefas quanto o número de encadeamentos vezes o número de elementos a serem processados. Portanto, nos códigos de tarefa do OpenMP você encontrará algo semelhante a
#pragma omp parallel
#pragma omp single
...
#pragma omp task
{ code for a given task; }
...
Processamento paralelo de um container de lista c++ usando tarefas OpenMP
#include <omp.h>
#include <unistd.h>
#include <iostream>
#include <list>
static void processElement (unsigned n)
{
// Tell who am I. The #pragma omp critical ensures that
// only one thread sends data to std::cout
#pragma omp critical
std::cout <<
"Thread " << omp_get_thread_num() << " processing element " << n
<< std::endl;
// Simulate some work
usleep (n*1000);
}
int main (void)
{
std::list<unsigned> lst;
// Fill the list
for (unsigned u = 0; u < 16; ++u)
lst.push_back (1+u);
// Now process each element of the list in parallel
#pragma omp parallel // Create a parallel region
#pragma omp single // Only one thread will instantiate tasks
{
for (auto element : lst)
{
#pragma omp task firstprivate (element)
processElement (element);
}
// Wait for all tasks to be finished
#pragma omp taskwait
}
return 0;
}
Este exemplo simula o processamento de uma lista STL (chamada lst
no código) em paralelo através das construções de tarefas OpenMP (usando a diretiva #pragma omp task
). O exemplo cria/instancia uma tarefa OpenMP para cada elemento em lst
e as threads OpenMP executam as tarefas assim que estiverem prontas para serem executadas.
$ OMP_NUM_THREADS=4 ./a.out
Thread 0 processing element 16
Thread 3 processing element 3
Thread 2 processing element 1
Thread 1 processing element 2
Thread 2 processing element 4
Thread 1 processing element 5
Thread 3 processing element 6
Thread 2 processing element 7
Thread 1 processing element 8
Thread 3 processing element 9
Thread 2 processing element 10
Thread 1 processing element 11
Thread 0 processing element 15
Thread 3 processing element 12
Thread 2 processing element 13
Thread 1 processing element 14
Cálculo recursivo para pi usando tarefas OpenMP
O código abaixo calcula o valor de PI usando uma abordagem recursiva. Modifique o valor MAX_PARALLEL_RECURSIVE_LEVEL
para determinar em qual profundidade de recursão para de criar tarefas. Com essa abordagem para criar paralelismo a partir de aplicativos recursivos: quanto mais tarefas você cria, mais tarefas paralelas são criadas, mas também menos trabalho por tarefa. Portanto, é conveniente experimentar o aplicativo para entender em que nível a criação de outras tarefas não se beneficia em termos de desempenho.
#include <stdio.h>
#include <omp.h>
double pi_r (double h, unsigned depth, unsigned maxdepth, unsigned long long begin, unsigned long long niters)
{
if (depth < maxdepth)
{
double area1, area2;
// Process first half
#pragma omp task shared(area1)
area1 = pi_r (h, depth+1, maxdepth, begin, niters/2-1);
// Process second half
#pragma omp task shared(area2)
area2 = pi_r (h, depth+1, maxdepth, begin+niters/2, niters/2);
#pragma omp taskwait
return area1+area2;
}
else
{
unsigned long long i;
double area = 0.0;
for (i = begin; i <= begin+niters; i++)
{
double x = h * (i - 0.5);
area += (4.0 / (1.0 + x*x));
}
return area;
}
}
double pi (unsigned long long niters)
{
double res;
double h = 1.0 / (double) niters;
#pragma omp parallel shared(res)
{
#define MAX_PARALLEL_RECURSIVE_LEVEL 4
#pragma omp single
res = pi_r (h, 0, MAX_PARALLEL_RECURSIVE_LEVEL, 1, niters);
}
return res * h;
}
int main (int argc, char *argv[])
{
#define NITERS (100*1000*1000ULL)
printf ("PI (w/%d iters) is %lf\n", NITERS, pi(NITERS));
return 0;
}