OOPS ObjectOriented Parallel System Um framework de classes
OOPS Object-Oriented Parallel System Um framework de classes para programação científica paralela. Eloiza Helena Sonoda Orientador: Prof. Dr. Gonzalo Travieso Apoio financeiro
Introdução O desenvolvimento da tecnologia dos microprocessadores tem resultado em grande desempenho dos computadores. O processamento paralelo tem se mostrado bastante apropriado para satisfazer a demanda crescente de desempenho computacional. Aumento na disponibilidade de sistemas paralelos.
Introdução Aplicações com alta demanda de poder computacional tendem a ser desenvolvidas usando programação concorrente para execução paralela. A programação concorrente é bastante complexa e a responsabilidade de desenvolver aplicações paralelas eficientes acaba sendo do programador da aplicação. Tornam-se necessárias ferramentas apropriadas para auxiliar no
Abordagens para o desenvolvimento de ferramentas Compiladores paralelizantes. Diretivas de paralelização. Linguagens. Bibliotecas.
Motivação do trabalho Argumentamos que uma abordagem promissora é baseada em ferramentas convencionais de programação seqüencial acrescida de uma biblioteca de classes que forneça abstrações adequadas de concorrência.
OOPS Object-Oriented Parallel System Proposta. Conceitos básicos. Apresentação de classes e exemplos. Programa Usuário Framework OOPS Biblioteca MPI
Proposta Projeto e implementação do framework de classes OOPS para apoiar a programação científica paralela. Características: Voltado a aplicações científicas com necessidade de alto desempenho. Técnicas de orientação a objetos para organizar o desenvolvimento do programa e encapsular detalhes de paralelismo.
Proposta Memória distribuída e passagem de mensagens. Utiliza somente ferramentas convencionais de programação acrescida de uma biblioteca de classes. Envolvimento do programador com aspectos paralelos de projeto. Fornecimento de abstrações próximas aos modelos matemáticos.
Proposta Processadores virtuais. Grupos de processadores virtuais. Topologias. Contêineres distribuídos. Componentes paralelos. Composições dos componentes paralelos: Seqüencial. Concorrente.
Classes básicas OOPS: : Main Componente paralelo principal, realiza inicialização e finalização da máquina virtual paralela. OOPS: : Group Grupo de processadores virtuais. OOPS: : Sendable Classe básica para objetos que serão comunicados.
Classes básicas OOPS: : Partner Parceiro em uma comunicação. OOPS: : Workgroup Grupo de trabalho para comunicações. OOPS: : Topology Estrutura de comunicação entre os processadores de um grupo. OOPS: : Distribution Modos de distribuição de contêineres.
Topologias Derivadas de OOPS: : Topology. Fornece comunicação ponto a ponto entre os processadores de um grupo em um arranjo e comunicação coletiva em subgrupos. OOPS: : Topology. Plain OOPS: : Topology. Pipe. Ring OOPS: : Topology. Linear OOPS: : Topology. Ring OOPS: : Topology. Grid OOPS: : Topology. Torus
#include <iostream> #include <mpi++. h> int main(int argc, char **argv) { MPI: : Init(argc, argv); int size, my. ID; size = MPI: : COMM_WORLD. Get_size(); my. ID = MPI: : COMM_WORLD. Get_rank(); int x = 0, tag = 99; if (!my. ID) { std: : cout << "I have x = " << x << std: : endl; x++; MPI: : COMM_WORLD. Send(&x, 1, MPI: : INT, 1, tag); } else { MPI: : COMM_WORLD. Recv(&x, 1, MPI: : INT, my. ID - 1, tag); std: : cout << "I have x = " << x << std: : endl; x++; if (my. ID != (size - 1)) MPI: : COMM_WORLD. Send(&x, 1, MPI: : INT, my. ID + 1, tag); } MPI: : Finalize(); return 0; }
#include <iostream> #include <mpi++. h> int main(int argc, char **argv) { MPI: : Init(argc, argv); int size, my. ID; size = MPI: : COMM_WORLD. Get_size(); my. ID = MPI: : COMM_WORLD. Get_rank(); int next = my. ID + 1; int previous = my. ID - 1; if (!my. ID) previous = MPI: : PROC_NULL; if (my. ID == size - 1) next = MPI: : PROC_NULL; int x = 0, tag = 99; MPI: : COMM_WORLD. Recv(&x, 1, MPI: : INT, previous, tag); std: : cout << "I have x = " << x << std: : endl; x++; MPI: : COMM_WORLD. Send(&x, 1, MPI: : INT, next, tag); MPI: : Finalize(); return 0; }
#include <iostream> #include <oops> void OOPS: : Main: : execute. On(const OOPS: : Group *all. Procs) { OOPS: : Topology. Pipe topo(all. Procs); int x = 0; topo. from. Previous(x); std: : cout << "I have x = " << x << std: : endl; x++; topo. to. Next(x); }
Modos de distribuição dos contêineres Derivadas de OOPS: : Distribution. Tamanhos locais. Conversões entre índices locais e globais. OOPS: : Distribution. Blocked OOPS: : Distribution. Cyclic OOPS: : Distribution. None
Contêineres Distribuídos Coleções de dados parametrizados no tipo do elemento armazenado. Particionamento de dados determinado pela topologia e modo de distribuição. OOPS: : Vector<T> OOPS: : Vector. Repl<T> OOPS: : Matrix<T>
const int dim = 10000; void OOPS: : Main: : execute. On(const OOPS: : Group *all. Procs) { try { OOPS: : Topology. Linear topo(all. Procs); OOPS: : Distribution. Blocked block; OOPS: : Vector<double> v 1(dim, block, topo), v 2(dim, block, topo), v(dim, block, topo); std: : ifstream a 1("vector 1. dat"), a 2("vector 2. dat"); a 1 >> v 1; a 2 >> v 2; v = v 1 * v 2; std: : ofstream saida("result. dat"); saida << v; double s = v. sum(); if (topo. group()->my. ID() == 0) std: : cout << "Produto escalar = " << std: : endl; } catch(Error_Not. Positive e) {. . . código. . . } catch(. . . ) { std: : cout << "n. OOPS!!n " << std: : endl; } }
const int N = 1000; void OOPS: : Main: : execute. On(const OOPS: : Group *all. Procs) { try { OOPS: : Topology. Linear topo_linear(all. Procs); OOPS: : Topology. Grid topo_grid(all. Procs, all. Procs->size()); OOPS: : Distribution. Blocked block; OOPS: : Distribution. None none; OOPS: : Vector. Repl<double> a(N, topo_linear); OOPS: : Vector<double> b(N, block, topo_linear); std: : ifstream file_vec("vector. dat"); file_vec >> a; OOPS: : Matrix<double> m(N, N, block, none, topo_grid); std: : ifstream file_mat(“matrix. dat"); file_mat >> m; b = m * a; std: : ofstream saida("result. dat"); saida << b; } }
Estêncil
Atualização de bordas fantasmas
void OOPS: : Main: : execute. On(const OOPS: : Group *all. Procs) { try { OOPS: : Topology. Grid grid(all. Procs); OOPS: : Distribution. Blocked block; int M, N; double Ts, Ti, diff_limit; read. Args(M, N, Ts, Ti, diff_limit); int ghost = 1; OOPS: : Matrix<double> a(M, N, block, grid, ghost), b(M, N, block, grid, ghost); . . . inicialização das matrizes a e b. . . double max_diff; do { b. sync. Ghosts. Cart(); for (int i = 0; i < b. local. Row. Size(); i++) for (int j = 0; j < b. local. Col. Size(); j++) a. local(i, j) = (b. local(i, j-1) + b. local(i, j+1) + b. local(i-1, j) + b. local(i+1, j)) / 4. 0; a. sync. Ghosts. Cart(); max_diff = 0;
for (int i = 0; i < b. local. Row. Size(); i++) for (int j = 0; j < b. local. Col. Size(); j++) { b. local(i, j) = (a. local(i, j-1) + a. local(i, j+1) + a. local(i-1, j) + a. local(i+1, j)) / 4. 0; double diff = fabs(b. local(i, j) - a. local(i, j)); if (diff > max_diff) max_diff = diff; } grid. max(max_diff); } while (max_diff > diff_limit); std: : ofstream result("result. dat"); result << b; } }
Intertopologias Derivadas de OOPS: : Inter. Topology. Função parametrizada no tipo de componente OOPS: : Execute<T>(), com T apresentando um método estático T: : execute. On().
Composição seqüencial void OOPS: : Main: : execute. On(const OOPS: : Group *all. Procs) { try{ OOPS: : Topology. Grid topo_grid(all. Procs); OOPS: : Distribution. Blocked block; int N; OOPS: : Matrix<double> m(N, N, block, topo_grid); foo(m); bar(m); std: : ofstream result("result. dat"); result << m; } }
Composição concorrente class Inter. Topology. Pipe : public OOPS: : Inter. Topology { OOPS: : Partner *next, *prev; public: Inter. Topology. Pipe(const OOPS: : Group *g) : OOPS: : Inter. Topology(g) { int n = g->my. ID() + 1; int p = g->my. ID() - 1; if (g->is. First()) p = OOPS: : PARTNER_NULL; if (g->is. Last()) n = OOPS: : PARTNER_NULL; next = new OOPS: : Partner(g, n); prev = new OOPS: : Partner(g, p); } void to. Next(int &x) const { next->send(x); } void from. Previous(int &x) const { prev->recv(x); } };
class foo { public: static void execute. On(const OOPS: : Group *g, const Inter. Topology. Pipe *it); }; class bar { public: static void execute. On(const OOPS: : Group *g, const Inter. Topology. Pipe *it); }; void foo: : execute. On(const OOPS: : Group *g, const Inter. Topology. Pipe *it) { int x; . . . continuação do código. . . it->to. Next(x); } void bar: : execute. On(const OOPS: : Group *g, const Inter. Topology. Pipe *it) { int x; it->from. Previous(x); . . . continuação do código. . . }
void OOPS: : Main: : execute. On(const OOPS: : Group *all. Procs) { try{ int n = 2; int *IDs; IDs = new int[n]; IDs[0] = 0; IDs[1] = 2; OOPS: : Group *sub 1 = all. Procs->sub. Group(n, IDs); IDs[0] = 1; IDs[1] = 3; OOPS: : Group *sub 2 = all. Procs->sub. Group(n, IDs); delete IDs; Inter. Topology. Pipe *it = new Inter. Topology. Pipe(all. Procs); OOPS: : Execute<foo>(sub 1, it); OOPS: : Execute<bar>(sub 2, it); } }
Desempenho Programa para o cálculo do fractal de Mandelbrot. Cluster Beowulf com 8 nós de processamento. Testes realizados com matrizes 5000 x 5000.
int compute_mandel(const double x, const double y) { // retorna o valor de mandelbrot para o complexo (x + iy) } void OOPS: : Main: : execute. On(const OOPS: : Group *all. Procs) { try{ OOPS: : Topology. Grid grid(all. Procs); OOPS: : Distribution. Cyclic cyclic; int n_real, n_imag; double br, ur, bi, ui; read. Args(br, ur, bi, ui, n_real, n_imag); double rdelta = (ur - br) / n_real; double idelta = (ui - bi) / n_imag; OOPS: : Matrix<int> mandel(n_imag, n_real, cyclic, grid); for (int i = 0; i < mandel. local. Row. Size(); i++) for (int j = 0; j < mandel. local. Col. Size(); j++) { OOPS: : Matrix<int>: : Index gi = mandel. local. To. Global(i, j); mandel. local(i, j) = compute_mandel(br+(gi. col()*rdelta)+rdelta/2, ui-(gi. row()*idelta)-idelta/2); } std: : ofstream out. File("result. dat"); out. File << mandel; } }
int compute_mandel(const double x, const double y) { /* retorna o valor de mandelbrot para o complexo (x + iy)*/ } int main(int argc, char *argv[]) { double lr, ur, li, ui, dx, dy; int rr, ir; FILE *arq; int **result; int i, j; double x, y; int ix, iy; int NP, p, NPr, NPi, pr, pi, lrr, lir; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &NP); MPI_Comm_rank(MPI_COMM_WORLD, &p); dx = (ur - lr) / rr; dy = (ui - li) / ir; /* Compute process grid dimensions */ NPr = (int)sqrt(NP); while (NP % NPr != 0) NPr--; NPi = NP/NPr; /* Compute position of the process on the grid */ pi = p / NPr; pr = p % NPr;
/* Compute local sizes */ lir = ir/NPi + (pi < (ir % NPi)); lrr = rr/NPr + (pr < (rr % NPr)); /* Allocate local pixels */ result = (int**)malloc(lir * sizeof(int*)); for (i = 0; i < lir; i++) result[i] = (int*)malloc(lrr*sizeof(int)); /* Compute distributed mandelbrot (cyclic distribution) */ for (iy = 0; iy < lir; iy++) { y = ui - (iy*NPi+pi) * dy; for (ix = 0; ix < lrr; ix++) { x = lr + (ix*NPr+pr) * dx; result[iy][ix] = compute_mandel(x + dx/2, y - dy/2); } } if (p > 0) { MPI_Request rq[lir]; MPI_Status st; /* Send result to process 0 */ for (iy = 0; iy < lir; iy++) MPI_Isend(result[iy], lrr, MPI_INT, 0, 0, MPI_COMM_WORLD, &rq[iy]); for (iy = 0; iy < lir; iy++) MPI_Wait(&rq[iy], &st); }
else { /* Process 0: collect and write results */ int **finalresult; int partner; MPI_Status st; finalresult = (int**)malloc(ir * sizeof(int*)); for (i = 0; i < ir; i++) finalresult[i] = (int*)malloc(rr*sizeof(int)); for (iy = 0; iy < lir; iy++) for (ix = 0; ix < lrr; ix++) finalresult[iy*NPi][ix*NPr] = result[iy][ix]; for (partner = 1; partner < NP; partner++) { int ppi = partner / NPr; int ppr = partner % NPr; int plir = ir/NPi + (ppi < (ir % NPi)); int plrr = rr/NPr + (ppr < (rr % NPr)); for (iy = 0; iy < plir; iy++) MPI_Recv(result[iy], plrr, MPI_INT, partner, 0, MPI_COMM_WORLD, &st); for (iy = 0; iy < plir; iy++) for (ix = 0; ix < plrr; ix++) finalresult[iy*NPi+ppi][ix*NPr+ppr] = result[iy][ix]; } /* Create output file */ } MPI_Finalize(); return 0; }
Sobrecarga adicionada pelo OOPS
Sugestões de trabalhos futuros Extensão das classes desenvolvidas. Entrada e saída paralela. Balanceamento de cargas e criação dinâmica de tarefas. Estruturas de dados irregulares.
- Slides: 39