Programação Paralela e Distribuída Programação Híbrida com o e o OpenMP
e OpenMP Uma forma de executar um programa em paralelo num cluster multiprocessor é criar um processo por cada processador. Nesse caso, pode acontecer que vários processos executem no mesmo multiprocessor. Apesar disso, todas as interacções entre processos ocorre por troca de mensagens. 1
e OpenMP Por vezes, pode ser melhor construir um programa híbrido em que apenas um processo executa em cada multiprocessor e depois cada processo cria um conjunto de threads igual ao número de processadores do multiprocessor para executar as regiões paralelas do programa. 2
e OpenMP Como alternativa, podemos combinar ambas as estratégias e adaptar a divisão entre processos e threads de modo a optimizar a utilização dos recursos disponíveis sem que isso viole eventuais restrições ou requisitos do problema em causa. 3
e OpenMP A programação híbrida com o e o OpenMP oferece as seguintes vantagens:
e OpenMP A programação híbrida com o e o OpenMP oferece as seguintes vantagens: A programação híbrida adequa-se perfeitamente às arquitecturas actuais baseadas em clusters multiprocessor, pois permite diminuir o número de comunicações entre os diferentes nós e aumentar o desempenho de cada nó sem que isso acarrete um incremento considerável dos requisitos de memória.
e OpenMP A programação híbrida com o e o OpenMP oferece as seguintes vantagens: A programação híbrida adequa-se perfeitamente às arquitecturas actuais baseadas em clusters multiprocessor, pois permite diminuir o número de comunicações entre os diferentes nós e aumentar o desempenho de cada nó sem que isso acarrete um incremento considerável dos requisitos de memória. Aplicações que possuem dois níveis de paralelismo podem utilizar processos para explorar paralelismo de granularidade grossa/média, trocando mensagens ocasionalmente para sincronizar informação e/ou distribuir trabalho, e utilizar threads para explorar paralelismo de granularidade média/fina por partilha do espaço de endereçamento.
e OpenMP A programação híbrida com o e o OpenMP oferece as seguintes vantagens: A programação híbrida adequa-se perfeitamente às arquitecturas actuais baseadas em clusters multiprocessor, pois permite diminuir o número de comunicações entre os diferentes nós e aumentar o desempenho de cada nó sem que isso acarrete um incremento considerável dos requisitos de memória. Aplicações que possuem dois níveis de paralelismo podem utilizar processos para explorar paralelismo de granularidade grossa/média, trocando mensagens ocasionalmente para sincronizar informação e/ou distribuir trabalho, e utilizar threads para explorar paralelismo de granularidade média/fina por partilha do espaço de endereçamento. Aplicações que tenham restrições ou requisitos que possam limitar o número de processos que podem ser usados (como por exemplo o algoritmo de Fox), podem tirar partido do OpenMP para explorar o poder computacional dos restantes processadores disponíveis.
e OpenMP A programação híbrida com o e o OpenMP oferece as seguintes vantagens: A programação híbrida adequa-se perfeitamente às arquitecturas actuais baseadas em clusters multiprocessor, pois permite diminuir o número de comunicações entre os diferentes nós e aumentar o desempenho de cada nó sem que isso acarrete um incremento considerável dos requisitos de memória. Aplicações que possuem dois níveis de paralelismo podem utilizar processos para explorar paralelismo de granularidade grossa/média, trocando mensagens ocasionalmente para sincronizar informação e/ou distribuir trabalho, e utilizar threads para explorar paralelismo de granularidade média/fina por partilha do espaço de endereçamento. Aplicações que tenham restrições ou requisitos que possam limitar o número de processos que podem ser usados (como por exemplo o algoritmo de Fox), podem tirar partido do OpenMP para explorar o poder computacional dos restantes processadores disponíveis. Aplicações cujo balanceamento de carga seja difícil de conseguir apenas por utilização de processos, podem tirar partido do OpenMP para equilibrar esse balanceamento, atribuindo um diferente número de threads a diferentes processos em função da respectiva carga. 4
e OpenMP A maneira mais simples e segura de combinar o com o OpenMP é utilizar as directivas apenas fora das regiões paralelas do OpenMP. Quando isso acontece, não há qualquer problema nas chamadas à biblioteca do, pois apenas o master thread está activo durante todas as comunicações. #include <mpi.h> #include <omp.h> // incluir a biblioteca de funç~oes // incluir a biblioteca de funç~oes OpenMP main(int argc, char **argv) { _Init(&argc, &argv); // apenas master thread --> chamadas a funç~oes aqui #pragma omp parallel { // team of threads --> nenhuma chamada a funç~oes aqui } // apenas master thread --> chamadas a funç~oes aqui _Finalize(); } 5
Produto Matriz-Vector (mpi omp produto.c) Sejam matrix[rows,cols] e vector[cols] respectivamente uma matriz e um vector coluna. O produto matriz-vector é um vector linha result[rows] em que cada result[i] é o produto escalar da linha i da matriz pelo vector. Se tivermos P processos e T threads por processo, cada processo pode calcular P ROWS (ROWS/P) elementos do vector resultado e cada thread pode calcular T ROWS (P ROWS/T) elementos do vector resultado.
Produto Matriz-Vector (mpi omp produto.c) Sejam matrix[rows,cols] e vector[cols] respectivamente uma matriz e um vector coluna. O produto matriz-vector é um vector linha result[rows] em que cada result[i] é o produto escalar da linha i da matriz pelo vector. Se tivermos P processos e T threads por processo, cada processo pode calcular P ROWS (ROWS/P) elementos do vector resultado e cada thread pode calcular T ROWS (P ROWS/T) elementos do vector resultado. // distribuir a matriz _Scatter(matrix, P_ROWS * COLS, _INT, submatriz, P_ROWS * COLS, _INT, ROOT, _COMM_WORLD); // calcular o produto matriz-vector #pragma omp parallel for num_threads(t) for (i = 0; i < P_ROWS; i++) subresult[i] = produto_escalar(&submatriz[i * COLS], vector, COLS); // recolher as sub-matrizes _Gather(subresult, P_ROWS, _INT, result, P_ROWS, _INT, ROOT, _COMM_WORLD); 6
Multiplicação de Matrizes com o Algoritmo de Fox Para matrizes de dimensão A[N][N] e B[N][N] e P processos, o algoritmo de Fox divide as matrizes em P sub-matrizes de dimensão (N/Q)*(N/Q), em que Q*Q=P, e atribui cada uma delas aos P processos disponíveis no sistema. Para calcular o resultado da multiplicação das suas sub-matrizes, cada processo apenas necessita de trocar informação com os processos da mesma linha e da mesma coluna de sub-matrizes que as suas. for (stage = 0; stage < Q; stage++) { bcast = // escolher uma sub-matriz A em cada linha de processos _Bcast(); // e enviá-la para todos os processos na mesma linha // multiplicar a sub-matriz A recebida com a actual sub-matriz B #pragma omp parallel for private(i,j,k) for (i = 0; i < N/Q; i++) for (j = 0; j < N/Q; j++) for (k = 0; k < N/Q; k++) C[i][j] += A[i][k] * B[k][j]; _Send() // enviar a sub-matriz B para o processo acima _Recv() // e receber a sub-matriz B do processo abaixo } 7
Safe Por outro lado, se um programa for paralelizado de modo a ter chamadas à biblioteca do dentro das regiões paralelas do OpenMP, então vários threads podem chamar as mesmas directivas do e ao mesmo tempo. Para que tal seja possível, é necessário que a implementação do seja thread safe. #pragma omp parallel private(tid) { tid = omp_get_thread_num(); if (tid == id1) call_mpi_function1() // thread id1 chama funç~ao aqui else if (tid == id2) call_mpi_function2() // thread id2 chama funç~ao aqui else do_something(); }
Safe Por outro lado, se um programa for paralelizado de modo a ter chamadas à biblioteca do dentro das regiões paralelas do OpenMP, então vários threads podem chamar as mesmas directivas do e ao mesmo tempo. Para que tal seja possível, é necessário que a implementação do seja thread safe. #pragma omp parallel private(tid) { tid = omp_get_thread_num(); if (tid == id1) call_mpi_function1() // thread id1 chama funç~ao aqui else if (tid == id2) call_mpi_function2() // thread id2 chama funç~ao aqui else do_something(); } A especificação -1 não define qualquer tipo de suporte para multithreading. Esse tipo de suporte foi apenas considerado a partir da especificação -2 por utilização da chamada Init thread() em lugar da chamada Init(). 8
Iniciar o com Suporte para Multithreading int Init thread(int *argc, char ***argv, int required, int *provided) Init thread() inicia o ambiente de execução do (tal como o Init()) e define o nível de suporte para multithreading.
Iniciar o com Suporte para Multithreading int Init thread(int *argc, char ***argv, int required, int *provided) Init thread() inicia o ambiente de execução do (tal como o Init()) e define o nível de suporte para multithreading. required é o nível de suporte pretendido. provided é o nível de suporte disponibilizado pela implementação do.
Iniciar o com Suporte para Multithreading int Init thread(int *argc, char ***argv, int required, int *provided) Init thread() inicia o ambiente de execução do (tal como o Init()) e define o nível de suporte para multithreading. required é o nível de suporte pretendido. provided é o nível de suporte disponibilizado pela implementação do. O nível de suporte para multithreading pode ser: THREAD SINGLE: apenas um thread irá executar. É o mesmo que iniciar o ambiente de execução com o Init(). THREAD FUNNELED: apenas o master thread pode fazer chamadas. THREAD SERIALIZED: todos os threads podem fazer chamadas, mas apenas um thread de cada vez pode estar nessa situação. THREAD MULTIPLE: todos os threads podem fazer chamadas em simultâneo e sem qualquer tipo de restrições. 9
THREAD FUNNELED Com suporte THREAD FUNNELED apenas o master thread pode fazer chamadas.
THREAD FUNNELED Com suporte THREAD FUNNELED apenas o master thread pode fazer chamadas. Uma forma de garantir isso é proteger todas as chamadas com a directiva #pragma omp master do OpenMP que permite definir blocos de código que devem ser executados apenas pelo master thread.
THREAD FUNNELED Com suporte THREAD FUNNELED apenas o master thread pode fazer chamadas. Uma forma de garantir isso é proteger todas as chamadas com a directiva #pragma omp master do OpenMP que permite definir blocos de código que devem ser executados apenas pelo master thread. No entanto, como a directiva #pragma omp master não define qualquer barreira impĺıcita de sincronização entre os threads, é necessário utilizar a directiva #pragma omp barrier para definir duas barreiras de sincronização expĺıcitas entre todos os threads da região paralela (à entrada e à saída da directiva #pragma omp master) de modo a proteger a chamada.
THREAD FUNNELED Com suporte THREAD FUNNELED apenas o master thread pode fazer chamadas. Uma forma de garantir isso é proteger todas as chamadas com a directiva #pragma omp master do OpenMP que permite definir blocos de código que devem ser executados apenas pelo master thread. No entanto, como a directiva #pragma omp master não define qualquer barreira impĺıcita de sincronização entre os threads, é necessário utilizar a directiva #pragma omp barrier para definir duas barreiras de sincronização expĺıcitas entre todos os threads da região paralela (à entrada e à saída da directiva #pragma omp master) de modo a proteger a chamada. #pragma omp parallel { #pragma omp barrier // barreira explícita à entrada #pragma omp master // apenas o master thread faz a chamada call_mpi_function() #pragma omp barrier // barreira explícita à saída } 10
THREAD SERIALIZED Com suporte THREAD SERIALIZED todos os threads podem fazer chamadas, mas apenas um thread de cada vez pode estar nessa situação.
THREAD SERIALIZED Com suporte THREAD SERIALIZED todos os threads podem fazer chamadas, mas apenas um thread de cada vez pode estar nessa situação. Uma forma de garantir isso é proteger todas as chamadas com a directiva #pragma omp single do OpenMP que permite definir blocos de código que devem ser executados por apenas um thread.
THREAD SERIALIZED Com suporte THREAD SERIALIZED todos os threads podem fazer chamadas, mas apenas um thread de cada vez pode estar nessa situação. Uma forma de garantir isso é proteger todas as chamadas com a directiva #pragma omp single do OpenMP que permite definir blocos de código que devem ser executados por apenas um thread. No entanto, como a directiva #pragma omp single não define uma barreira impĺıcita de sincronização à entrada da directiva, é necessário utilizar a directiva #pragma omp barrier para definir essa barreira de sincronização expĺıcita entre todos os threads da região paralela (à entrada da directiva #pragma omp single) de modo a proteger a chamada.
THREAD SERIALIZED Com suporte THREAD SERIALIZED todos os threads podem fazer chamadas, mas apenas um thread de cada vez pode estar nessa situação. Uma forma de garantir isso é proteger todas as chamadas com a directiva #pragma omp single do OpenMP que permite definir blocos de código que devem ser executados por apenas um thread. No entanto, como a directiva #pragma omp single não define uma barreira impĺıcita de sincronização à entrada da directiva, é necessário utilizar a directiva #pragma omp barrier para definir essa barreira de sincronização expĺıcita entre todos os threads da região paralela (à entrada da directiva #pragma omp single) de modo a proteger a chamada. #pragma omp parallel { #pragma omp barrier #pragma omp single call_mpi_function() } // barreira explícita à entrada // apenas um thread faz a chamada // barreira implícita à saída 11
THREAD MULTIPLE Com suporte THREAD MULTIPLE todos os threads podem fazer chamadas em simultâneo e sem qualquer tipo de restrições.
THREAD MULTIPLE Com suporte THREAD MULTIPLE todos os threads podem fazer chamadas em simultâneo e sem qualquer tipo de restrições. Como a implementação é thread safe não é necessário nenhum mecanismo adicional de sincronização entre os threads da região paralela de modo a proteger as chamadas.
THREAD MULTIPLE Com suporte THREAD MULTIPLE todos os threads podem fazer chamadas em simultâneo e sem qualquer tipo de restrições. Como a implementação é thread safe não é necessário nenhum mecanismo adicional de sincronização entre os threads da região paralela de modo a proteger as chamadas. #pragma omp parallel private(tid) { tid = omp_get_thread_num(); if (tid == id1) call_mpi_function1() // thread id1 chama funç~ao aqui else if (tid == id2) call_mpi_function2() // thread id2 chama funç~ao aqui else do_something(); } 12
THREAD MULTIPLE A comunicação entre threads de diferentes processos levanta o problema de identificar o thread que está envolvido na comunicação, pois as funções de comunicação do apenas têm argumentos para identificar o ranking dos processos.
THREAD MULTIPLE A comunicação entre threads de diferentes processos levanta o problema de identificar o thread que está envolvido na comunicação, pois as funções de comunicação do apenas têm argumentos para identificar o ranking dos processos. Uma forma simples de resolver esse problema é utilizar o argumento tag das funções de comunicação como forma de identificar os threads envolvidos nas comunicações.
THREAD MULTIPLE A comunicação entre threads de diferentes processos levanta o problema de identificar o thread que está envolvido na comunicação, pois as funções de comunicação do apenas têm argumentos para identificar o ranking dos processos. Uma forma simples de resolver esse problema é utilizar o argumento tag das funções de comunicação como forma de identificar os threads envolvidos nas comunicações. _Init_thread(&argc, &argv, _THREAD_MULTIPLE, &thread_level); _Comm_rank(_COMM_WORLD, &my_rank); #pragma omp parallel num_threads(nthreads) private(tid) { tid = omp_get_thread_num(); if (my_rank == 0) // o processo 0 envia NTHREADS mensagens _Send(a, 1, _INT, 1, tid, _COMM_WORLD); else if (my_rank == 1) // o processo 1 recebe NTHREADS mensagens _Recv(b, 1, _INT, 0, tid, _COMM_WORLD, &status); } 13