• Nenhum resultado encontrado

Hipótese 5 (H5): Se bem estruturado, um modelo automático para criação de

6. MAPEAMENTO HÍBRIDO

O desenvolvimento dos modelos de mapeamento de processos pesados e de ma- peamento de memória foram desenvolvidos de forma independente por questões de testes e modularidade. Além disto, com os modelos separados foi possível identificar os aspectos que eram estritamente relacionados a cada um, permitindo a geração automática de código para diferentes arquiteturas.

A integração destes dois mapeamentos culmina no objetivo principal do trabalho: geração automática de código paralelo para arquiteturas híbridas, utilizando afinidade de memória. Este capítulo descreve o desenvolvimento do mapeamento híbrido, desenvolvido com o intuito de permitir ao programador que utilize melhor os recursos disponíveis em clus- ters de máquinas NUMA. Neste sentido, os modelos de mapeamento de processos pesados (Capítulo 4) e de mapeamento de memória (Capítulo 5) foram de extrema importância.

Este capítulo apresenta a integração entre os mapeamentos de processos pesa- dos e de memória, bem como um exemplo de utilização.

6.1 Visão geral do mapeamento híbrido

Conforme descrito anteriormente, arquiteturas do tipo cluster de máquinas NUMA possuem dois níveis de paralelismo: inter e intranodo. Para o paralelismo internodo, o ma- peamento híbrido permite que o usuário crie o modelo do programa paralelo através da definição de um grafo dirigido. Neste grafo, os nodos representam processos (geralmente mapeados para diferentes máquinas do cluster) e os arcos representam as comunicações entre eles. O mapeamento de memória, por sua vez, permite que o usuário aplique políti- cas de afinidade de memória no nível intranodo. Neste contexto, o usuário define a política, quantidade de threads e quais estruturas devem serem alocadas e o modelo de mapea- mento gera o código final. A Figura 6.1 apresenta os dois mapeamentos lado a lado.

Como pode ser visto, o mapeamento de processos pesados (Figura 6.1a) gera arquivos template, que o usuário deve completar com seu código sequencial. Estes códigos, então, poderão ser executados em um cluster. O mapeamento de memória (Figura 6.1b) permite que um usuário aplique uma determinada política de afinidade de memória no seu código sequencial, para que ele tenha um desempenho mais satisfatório. O código com as políticas aplicadas está pronto para ser executado em uma máquina com arquitetura NUMA. Contudo, as máquinas de um cluster podem possuir arquitetura NUMA. Neste ce- nário, os dois mapeamentos separadamente não conseguem atingir todas as funcionalida- des da arquitetura disponível. A criação do modelo de mapeamento híbrido objetiva explo- rar o desenvolvimento de aplicações híbridas, que utilizem os dois níveis de paralelismo, e baseou-se na integração entre dos dois mapeamentos supracitados. Assim sendo, para a programação do nível internodo, utiliza-se um grafo dirigido. Entretanto, como cada nodo deste grafo representa um processo que pode ser associado a uma máquina do tipo NUMA, em cada nodo é permitido ao usuário definir a política de memória que deseja aplicar. A Figura 6.2 apresenta um diagrama ilustrando a visão geral do mapeamento híbrido.

Figura 6.2 – Visão geral do mapeamento híbrido.

Para integrar os mapeamentos, foi necessário definir uma forma de “comunicação” entre a parte responsável pelo desenvolvimento intranodo com aquela responsável pelo desenvolvimento internodo. Isto se deve ao fato de que, neste mapeamento, cada nodo do grafo deixa de ser uma máquina comum e passa a ser uma máquina NUMA.

De acordo com a Figura 6.2, tem-se que o usuário interage normalmente com o mapeamento de processos pesados com padrões, indicando o grafo da aplicação e as configurações de comunicação. Em seguida, é gerado um arquivo template. Este arquivo template possui métodos que devem ser preenchidos com código sequencial do usuário.

Neste momento, entra o mapeamento de memória. O usuário então, deve inte- ragir novamente, desta vez informando tudo o que é necessário para que as estruturas sejam alocadas corretamente. Assim sendo, o usuário deve definir a política de afinidade de memória a ser utilizada, a quantidade de nodos NUMA e a quantidade de threads. Com isto, é gerado o código transformado, que serve tanto para comunicação entre as máqui-

nas do cluster quanto para a comunicação interna de cada nodo, caracterizando, assim, a aplicação híbrida.

Cabe ressaltar que todos os aspectos detalhados no Capítulo 5 sobre a geração automática de código para máquinas NUMA, bem como todas as funcionalidades e os pa- drões de programação (skeletons) descritos no Capítulo 4 são utilizados da mesma maneira neste mapeamento. A integração dos modelos, no entanto, exige algumas mudanças na forma com que o usuário interage com o mapeamento. A Seção 6.2 apresenta um exemplo de utilização híbrido.

6.2 Exemplo simples de utilização

Para utilizar o mapeamento híbrido, conforme descrito anteriormente, deve-se in- formar um grafo para a aplicação paralela. Considere uma simples aplicação que apenas envia um array unidimensional para outra máquina, que processa o array e retorna um resultado. Será utilizado, para este exemplo, o padrão de programação Master/Slave. A Figura 6.3 apresenta um possível grafo para esta aplicação.

Figura 6.3 – Exemplo de um grafo com 5 nodos.

No exemplo apresentado, existe a figura de um mestre que deve comunicar-se com 4 escravos. Após este passo, deve ser definido o tipo da comunicação intranodo. Conforme supracitado, será um array unidimensional, e será definido que ele é do tipo double. Com isto, o template gerado para a aplicação teria um formato semelhante ao ilustrado na Figura 6.4.

Os passos realizados até então utilizam o mapeamento de processos pesados com padrões. Quando o usuário informar o código a ser incluído no template, ele poderá informar uma política de alocação de memória para os dados a serem alocados. Assim, supondo a escolha da política bind_block, um exemplo de código transformado automatica- mente pelo mapeamento de memória pode ser visto na Figura 6.5.

É importante notar que não deve ser utilizado malloc ou qualquer outra forma de alocação dos arrays, pois não é garantido que as páginas serão untouched. Por isto, as chamadas a allocate_structures são realizadas, pois alocarão a memória com as funções específicas da MAI.

Figura 6.4 – Template gerado pelo mapeamento de processos pesados com padrões.

O mesmo procedimento deve ser realizado para o outro nodo do grafo: incluir o código sequencial e escolher as configurações de memória desejadas. Ao final, os arquivos de cada nodo serão mapeados para um processo MPI (controlado pela classe Control), e nos nodos do array serão alocados utilizando a política bind_block.

Figura 6.5 – Template gerado pelo mapeamento de memória.

6.3 Considerações finais

Analisando os dois exemplos apresentados na Seção 6.2, é possível notar que a afinidade de memória não precisa obrigatoriamente ser utilizada, sendo possível escolher apenas o mapeamento entre os processos pesados, por exemplo.

Da mesma forma, é possível a criação de um único nodo no grafo de entrada. Desta forma, haverá apenas uma máquina no modelo, e é possível utilizar apenas o mape- amento de memória para máquinas NUMA, escolhando a política de afinidade desejada.

Estas são decisões do usuário, cabendo a ele escolher o que necessita utilizar da ferramenta. A forma de utilização de cada um dos mapeamentos através de uma interface gráfica (desenvolvida no presente estudo) é detalhada no Capítulo 7.

Documentos relacionados