390 likes | 512 Views
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.
E N D
OOPSObject-Oriented Parallel SystemUm 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 desenvolvimento de aplicações paralelas.
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::TopologyPlain • OOPS::TopologyPipe • OOPS::TopologyPipeRing • OOPS::TopologyLinear • OOPS::TopologyRing • OOPS::TopologyGrid • OOPS::TopologyTorus
#include <iostream> #include <mpi++.h> int main(int argc, char **argv) { MPI::Init(argc, argv); int size, myID; size = MPI::COMM_WORLD.Get_size(); myID = MPI::COMM_WORLD.Get_rank(); int x = 0, tag = 99; if (!myID) { 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, myID - 1, tag); std::cout << "I have x = " << x << std::endl; x++; if (myID != (size - 1)) MPI::COMM_WORLD.Send(&x, 1, MPI::INT, myID + 1, tag); } MPI::Finalize(); return 0; }
#include <iostream> #include <mpi++.h> int main(int argc, char **argv) { MPI::Init(argc, argv); int size, myID; size = MPI::COMM_WORLD.Get_size(); myID = MPI::COMM_WORLD.Get_rank(); int next = myID + 1; int previous = myID - 1; if (!myID) previous = MPI::PROC_NULL; if (myID == 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::executeOn(const OOPS::Group *allProcs) { OOPS::TopologyPipe topo(allProcs); int x = 0; topo.fromPrevious(x); std::cout << "I have x = " << x << std::endl; x++; topo.toNext(x);}
Modos de distribuição dos contêineres • Derivadas de OOPS::Distribution. • Tamanhos locais. • Conversões entre índices locais e globais. • OOPS::DistributionBlocked • OOPS::DistributionCyclic • OOPS::DistributionNone
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::VectorRepl<T> • OOPS::Matrix<T>
const int dim = 10000; void OOPS::Main::executeOn(const OOPS::Group *allProcs) { try { OOPS::TopologyLinear topo(allProcs); OOPS::DistributionBlocked block;OOPS::Vector<double> v1(dim, block, topo), v2(dim, block, topo), v(dim, block, topo); std::ifstream a1("vector1.dat"), a2("vector2.dat"); a1 >> v1; a2 >> v2;v = v1 * v2; std::ofstream saida("result.dat"); saida << v;double s = v.sum(); if (topo.group()->myID() == 0) std::cout << "Produto escalar = " << s << std::endl;} catch(Error_NotPositive e) { ...código... } catch(...) { std::cout << "\nOOPS!!\n " << std::endl; } }
const int N = 1000; void OOPS::Main::executeOn(const OOPS::Group *allProcs) { try { OOPS::TopologyLinear topo_linear(allProcs); OOPS::TopologyGrid topo_grid(allProcs, allProcs->size()); OOPS::DistributionBlocked block; OOPS::DistributionNone none; OOPS::VectorRepl<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; }}
void OOPS::Main::executeOn(const OOPS::Group *allProcs) { try { OOPS::TopologyGridgrid(allProcs); OOPS::DistributionBlocked block; int M, N; double Ts, Ti, diff_limit; readArgs(M, N, Ts, Ti, diff_limit); int ghost = 1; OOPS::Matrix<double> a(M, N, block, block, grid, ghost), b(M, N, block, block, grid, ghost); ... inicialização das matrizes a e b ... double max_diff; do { b.syncGhostsCart(); for (int i = 0; i < b.localRowSize(); i++) for (int j = 0; j < b.localColSize(); 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.syncGhostsCart(); max_diff = 0;
for (int i = 0; i < b.localRowSize(); i++) for (int j = 0; j < b.localColSize(); 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::InterTopology. • Função parametrizada no tipo de componente OOPS::Execute<T>(), com T apresentando um método estático T::executeOn().
Composição seqüencial void OOPS::Main::executeOn(const OOPS::Group *allProcs) { try{ OOPS::TopologyGrid topo_grid(allProcs); OOPS::DistributionBlocked block; int N; OOPS::Matrix<double> m(N, N, block, block, topo_grid); foo(m); bar(m); std::ofstream result("result.dat");result << m; }}
Composição concorrente class InterTopologyPipe : public OOPS::InterTopology { OOPS::Partner *next, *prev;public: InterTopologyPipe(const OOPS::Group *g) : OOPS::InterTopology(g) { int n = g->myID() + 1; int p = g->myID() - 1; if (g->isFirst()) p = OOPS::PARTNER_NULL; if (g->isLast()) n = OOPS::PARTNER_NULL; next = new OOPS::Partner(g, n); prev = new OOPS::Partner(g, p); } void toNext(int &x) const { next->send(x); } void fromPrevious(int &x) const { prev->recv(x); }};
class foo {public:static void executeOn(const OOPS::Group *g, const InterTopologyPipe *it);};class bar {public:static void executeOn(const OOPS::Group *g, const InterTopologyPipe *it);};void foo::executeOn(const OOPS::Group *g, const InterTopologyPipe *it) {int x; ... continuação do código ...it->toNext(x);}void bar::executeOn(const OOPS::Group *g, const InterTopologyPipe *it) { int x; it->fromPrevious(x); ... continuação do código ...}
void OOPS::Main::executeOn(const OOPS::Group *allProcs) { try{ int n = 2; int *IDs; IDs = new int[n]; IDs[0] = 0; IDs[1] = 2; OOPS::Group *sub1 = allProcs->subGroup(n, IDs); IDs[0] = 1; IDs[1] = 3; OOPS::Group *sub2 = allProcs->subGroup(n, IDs); delete IDs; InterTopologyPipe *it = new InterTopologyPipe(allProcs); OOPS::Execute<foo>(sub1, it); OOPS::Execute<bar>(sub2, it);}}
Desempenho • Programa para o cálculo do fractal de Mandelbrot. • Cluster Beowulf com 8 nós de processamento. • Testes realizados com matrizes 5000x5000.
int compute_mandel(const double x, const double y) {// retorna o valor de mandelbrot para o complexo (x + iy)}void OOPS::Main::executeOn(const OOPS::Group *allProcs) { try{ OOPS::TopologyGrid grid(allProcs); OOPS::DistributionCyclic cyclic; int n_real, n_imag; double br, ur, bi, ui;readArgs(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, cyclic, grid); for (int i = 0; i < mandel.localRowSize(); i++) for (int j = 0; j < mandel.localColSize(); j++) { OOPS::Matrix<int>::Index gi = mandel.localToGlobal(i,j); mandel.local(i,j) = compute_mandel(br+(gi.col()*rdelta)+rdelta/2, ui-(gi.row()*idelta)-idelta/2); } std::ofstream outFile("result.dat");outFile << 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;}
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.