Diretiva parallel Diretiva parallel pragma omp parallel clauses

  • Slides: 39
Download presentation
Diretiva parallel

Diretiva parallel

Diretiva parallel #pragma omp parallel [clauses] { code_block } Define uma região paralela, que

Diretiva parallel #pragma omp parallel [clauses] { code_block } Define uma região paralela, que é o código que será executado por vários threads em paralelo.

// omp_parallel. cpp // compile with: /openmp Exemplo – Diretiva parallel #include <stdio. h>

// omp_parallel. cpp // compile with: /openmp Exemplo – Diretiva parallel #include <stdio. h> #include <omp. h> int main() { #pragma omp parallel num_threads(4) { int i = omp_get_thread_num(); printf_s("Hello from thread %dn", i); } }

 Por padrão, o número de threads é igual ao número de processadores lógicos

Por padrão, o número de threads é igual ao número de processadores lógicos no computador. Diretiva parallel Por exemplo, se você tiver uma máquina com um processador físico com hyperthreading habilitado, ele terá dois processadores lógicos e, portanto, duas threads. Hyperthreading - Simulando dois núcleos lógicos em um único núcleo físico, cada núcleo lógico recebe seu próprio controlador de interrupção programável, e um conjunto de registradores. Os outros recursos do núcleo físico como cache de memória, unidade lógica e aritmética, barramentos, são compartilhados entre os núcleos lógicos, parecendo assim um sistema com dois núcleos físicos.

 omp_get_thread_num() Retorna o número da thread em execução dentro de sua equipe de

omp_get_thread_num() Retorna o número da thread em execução dentro de sua equipe de threads em paralelo. Função omp_get_thread_num() Hello from thread 0 Hello from thread 1 Hello from thread 2 Hello from thread 3 Observe que a ordem de saída pode variar em máquinas diferentes. Não confundir com omp_get_num_threads() função retorna o número de threads, atualmente na equipe de threads executando na região paralela do qual ele é chamado.

Diretiva Open. MP for

Diretiva Open. MP for

Diretiva Open. MP for #pragma omp [parallel] for [clauses] for_statement Faz com que o

Diretiva Open. MP for #pragma omp [parallel] for [clauses] for_statement Faz com que o trabalho feito em um loop for dentro de uma região paralela seja dividido entre threads.

#pragma omp for (i = n. Start; i <= n. End; ++i) { #pragma

#pragma omp for (i = n. Start; i <= n. End; ++i) { #pragma omp atomic n. Sum += i; } Diretiva atomic - Especifica que um local de memória que será atualizado numa única etapa de processamento, relativa a outras threads. An operation acting on shared memory is atomic if it completes in a single step relative to other threads.

Operações agindo sobre memória compartilhada. Ver a diretiva Open. MP atomic

Operações agindo sobre memória compartilhada. Ver a diretiva Open. MP atomic

Diretiva master A diretiva master permite especificar que uma seção de código deve ser

Diretiva master A diretiva master permite especificar que uma seção de código deve ser executada em uma única thread, não necessariamente a thread principal.

int main( ) { Exemplo – Diretivas master e barrier int a[5], i; #pragma

int main( ) { Exemplo – Diretivas master e barrier int a[5], i; #pragma omp parallel { // Perform some computation. #pragma omp for (i = 0; i < 5; i++) a[i] = i * i;

Diretiva – barrier Sincroniza todos as threads em uma equipe; Todas as threads pausam

Diretiva – barrier Sincroniza todos as threads em uma equipe; Todas as threads pausam na barreira, até que todas as threads executem a barreira.

Exemplo – Diretivas master e barrier // Print intermediate results in a single thread.

Exemplo – Diretivas master e barrier // Print intermediate results in a single thread. #pragma omp master for (i = 0; i < 5; i++) printf_s("a[%d] = %dn", i, a[i]); // Wait. #pragma omp barrier // Continue with the computation. #pragma omp for (i = 0; i < 5; i++) a[i] += i; } }

Diretiva Schedule

Diretiva Schedule

#define THREADS 8 #define N 100 By default, Open. MP statically assigns loop iterations

#define THREADS 8 #define N 100 By default, Open. MP statically assigns loop iterations to threads. int main ( ) { int i; #pragma omp parallel for num_threads(THREADS) for (i = 0; i < N; i++) { printf( "Thread %d is doing iteration %d. n", omp_get_thread_num(), i ); } /* all threads done */ printf("All done!n"); return 0; }

#define THREADS 4 A static schedule can be nonoptimal, however. This is the case

#define THREADS 4 A static schedule can be nonoptimal, however. This is the case when the different iterations take different amounts of time. #define N 16 int main ( ) { int i; #pragma omp parallel for schedule(static) num_threads(THREADS) for (i = 0; i < N; i++) { /* wait for i seconds */ sleep(i); printf( "Thread %d has completed iteration %d. n", omp_get_thread_num( ), i); } /* all threads done */ printf("All done!n"); return 0; } This program also specifies static scheduling, in the parallel for directive. This program can be greatly improved with a dynamic schedule.

#define THREADS 4 #define N 16 int main ( ) { int i; #pragma

#define THREADS 4 #define N 16 int main ( ) { int i; #pragma omp parallel for schedule(dynamic) num_threads(THREADS) How much faster does this program run? for (i = 0; i < N; i++) { /* wait for i seconds */ sleep(i); printf( "Thread %d has completed iteration %d. n", omp_get_thread_num( ), i ); } /* all threads done */ printf("All done!n"); return 0; } How much faster does this program run?

 Dynamic scheduling is better when the iterations may take very different amounts of

Dynamic scheduling is better when the iterations may take very different amounts of time. Dynamic Schedule Overhead However, there is some overhead to dynamic scheduling. After each iteration, the threads must stop and receive a new value of the loop variable to use for its next iteration.

#define THREADS 16 #define N 10000 int main ( ) { The following program

#define THREADS 16 #define N 10000 int main ( ) { The following program demonstrates this overhead: int i; printf( "Running %d iterations on %d threads dynamically. n", N, THREADS); #pragma omp parallel for schedule(dynamic) num_threads(THREADS) for (i = 0; i < N; i++) { /* a loop that doesn't take very long */ } /* all threads done */ printf("All done!n"); return 0; } How long does this program take to execute?

If we specify static scheduling, the program will run faster: #define THREADS 16 #define

If we specify static scheduling, the program will run faster: #define THREADS 16 #define N 10000 int main ( ) { int i; printf( "Running %d iterations on %d threads statically. n", N, THREADS); #pragma omp parallel for schedule(static) num_threads(THREADS) for (i = 0; i < N; i++) { /* a loop that doesn't take very long */ } /* all threads done */ printf("All done!n"); return 0; }

 We can split the difference between static and dynamic scheduling by using chunks

We can split the difference between static and dynamic scheduling by using chunks in a dynamic schedule. Chunk Sizes Here, each thread will take a set number of iterations, called a “chunk”, execute it, and then be assigned a new chunk when it is done.

By specifying a chunk size of 100 in the program below, we markedly improve

By specifying a chunk size of 100 in the program below, we markedly improve the performance: #define THREADS 16 #define N 10000 #define CHUNK 100 int main ( ) { int i; printf("Running %d iterations on %d threads dynamically. n", N, THREADS); #pragma omp parallel for schedule(dynamic, CHUNK) num_threads(THREADS) for (i = 0; i < N; i++) { /* a loop that doesn't take very long */ } /* all threads done */ printf("All done!n"); return 0; }

Increasing or decreasing the chunk size. . . Increasing the chunk size makes the

Increasing or decreasing the chunk size. . . Increasing the chunk size makes the scheduling more static, and decreasing it makes it more dynamic.

 Instead of static, or dynamic, we can specify guided as the schedule. Guided

Instead of static, or dynamic, we can specify guided as the schedule. Guided Schedules This scheduling policy is similar to a dynamic schedule, except that the chunk size changes as the program runs. It begins with big chunks, but then adjusts to smaller chunk sizes if the workload is imbalanced.

Guided Schedules How does the program above perform with a guided schedule?

Guided Schedules How does the program above perform with a guided schedule?

Guided Schedules #define THREADS 16 #define N 10000 int main ( ) { int

Guided Schedules #define THREADS 16 #define N 10000 int main ( ) { int i; printf("Running %d iterations on %d threads guided. n", N, THREADS); #pragma omp parallel for schedule(guided) num_threads(THREADS) for (i = 0; i < N; i++) { /* a loop that doesn't take very long */ } /* all threads done */ printf("All done!n"); return 0; }

How does our program with iterations that take different amounts of time perform with

How does our program with iterations that take different amounts of time perform with guided scheduling? #define THREADS 4 #define N 16 int main ( ) { int i; #pragma omp parallel for schedule(guided) num_threads(THREADS) for (i = 0; i < N; i++) { /* wait for i seconds */ sleep(i); printf("Thread %d has completed iteration %d. n", omp_get_thread_num( ), i); } /* all threads done */ printf("All done!n"); return 0; }

 Open. MP for automatically splits for loop iterations for us. Conclusion But, depending

Open. MP for automatically splits for loop iterations for us. Conclusion But, depending on our program, the default behavior may not be ideal. For loops where each iteration takes roughly equal time, static schedules work best, as they have little overhead.

 For loops where each iteration can take very different amounts of time, dynamic

For loops where each iteration can take very different amounts of time, dynamic schedules, work best as the work will be split more evenly across threads. Scheduled Conclusion Specifying chunks, or using a guided schedule provide a trade-off (uma alternativa) between the two. Choosing the best schedule depends on understanding your loop.

 2 problemas: inicialização + valor final! int soma = 0 ; #pragma omp

2 problemas: inicialização + valor final! int soma = 0 ; #pragma omp parallel for schedule(static) private(soma) Pr ivate for (i=0 ; i < 10000 ; i++) soma += a[i]; printf(“Terminado — soma = %d”, soma);

int soma = 0 ; #pragma omp parallel for schedule(static) #pragma omp firstprivate(soma) lastprivate(soma)

int soma = 0 ; #pragma omp parallel for schedule(static) #pragma omp firstprivate(soma) lastprivate(soma) for (i=0 ; i < 10000 ; i++) soma += a[i]; printf(“Terminado”); Resolveu o problema da inicialização e do fim!

Firstprivate Especifica que cada thread deve ter sua própria instância de uma variável, e

Firstprivate Especifica que cada thread deve ter sua própria instância de uma variável, e que a variável deve ser inicializada com o valor da variável, pois ela existe antes da construção parallel.

Lastprivate Especifica que o contexto delimitador da variável é definida igual à versão particular

Lastprivate Especifica que o contexto delimitador da variável é definida igual à versão particular de qualquer thread que executa a iteração final (construção de loop).

reduction Especifica que uma ou mais variáveis que são particulares a cada thread são

reduction Especifica que uma ou mais variáveis que são particulares a cada thread são o assunto de uma operação de redução no final da região paralela.

 usada para operações tipo “all-to-one”: exemplo: op = ’+’ reduction (op : list);

usada para operações tipo “all-to-one”: exemplo: op = ’+’ reduction (op : list); cada thread terá uma cópia da(s) variável(is) definidas em ’list’ com a devida inicialização; ela efetuará a soma local com sua cópia; ao sair da seção paralela, as somas locais serão automaticamente adicionadas na variavel global.

#include #define NUM_THREADS 4 void main( ) { int i, tmp, res = 0;

#include #define NUM_THREADS 4 void main( ) { int i, tmp, res = 0; #pragma omp parallel for reduction(+: res) private(tmp) for (i=0 ; i< 10000 ; i++) { tmp = Calculo( ); res += tmp ; } printf(“O resultado vale %d´´, res) ; } Obs: os índices de laços sempre são privados.

nowait - Substitui a barreira implícita em uma diretiva. #include <stdio. h> #define SIZE

nowait - Substitui a barreira implícita em uma diretiva. #include <stdio. h> #define SIZE 5 void test( int *a, int *b, int *c, int size ) { int i; #pragma omp parallel { #pragma omp for nowait for (i = 0; i < size; i++) b[i] = a[i] * a[i]; #pragma omp for nowait for (i = 0; i < size; i++) c[i] = a[i]/2; } } Se houver vários loops independentes dentro de uma região paralela, você pode usar o nowait para evitar a barreira implícita no final do for, da seguinte maneira: . .

Parallel sections #pragma omp [parallel] sections [clauses] { #pragma omp section { code_block }

Parallel sections #pragma omp [parallel] sections [clauses] { #pragma omp section { code_block } }

Pode-se usar omp section quando não se usam laços: OMP SECTIONS #pragma omp parallel

Pode-se usar omp section quando não se usam laços: OMP SECTIONS #pragma omp parallel #pragma omp sections { Calculo 1( ); #pragma omp section Calculo 2( ); #pragma omp section Calculo 3( ); } As seções são distribuídas entre as diferentes threads. Cada seção tem uma lógica diferente (threads diferentes).