Programação Paralela em Arquiteturas Multi-Core/Compiladores paralelizadores: diferenças entre revisões

[edição não verificada][edição não verificada]
Conteúdo apagado Conteúdo adicionado
Brunofs (discussão | contribs)
Sem resumo de edição
Brunofs (discussão | contribs)
Sem resumo de edição
Linha 32:
Classicamente, um compilador traduz um programa de uma linguagem textual facilmente entendida por um ser humano para uma linguagem de máquina, específica para um processador e sistema operacional. Atualmente, porém, são comuns compiladores que geram código para uma máquina virtual que é, depois, interpretada por um interpretador.
 
Em linguagens de programação híbridas, o compilador tem o papel de converter o código fonte em um código chamado de ''bytecode'', que é uma linguagem de baixo nível. Um exemplo deste comportamento é o do compilador da linguagem Java que, em vez de gerar código da máquina hospedeira (onde se está executando o compilador), gera código chamado ''Java Bytecode''.
 
Outra parte separada do compilador que muitos usuários vêem como integrada é o linker, cuja função é unir vários programas já compilados de uma forma independente e unificá-los em um programa executável. Isso inclui colocar o programa final em um formato compatível com as necessidades do sistema operacional para carregá-lo em memória e colocá-lo em execução.
Linha 294:
O compilador pode reordenar as instruções para possiblitar a redução de uso de sincronizações. Quanto maior o número de sincronizações necessárias maior será o prejuízo para o desempenho do programa. Nesse ponto vê-se a importância de testes de dependências confiáveis, uma vez que testes mal feitos podem incluir sincronizações desnecessárias influenciando o desempenho final.
 
Um aliado dos compiladores paralelizadores, em especial em ambientes distribuídos, é o '''multithreading''', técnica utilizada para ocultar a latência de memória e sincronização. Dada uma ''thread'' que por algum motivo teve de ser bloqueada, o ''multithreading'' reduz os custos de requisições remotas executando outras ''threads'' enquanto a ''thread'' corrente estiver bloqueada.
 
O primeiro ganho de desempenho de ''multithreading'' é que várias ''threads'' podem ser usadas para garantir que o processamento seja realizado enquanto a ''thread'' correntemente ativa aguarda por uma requisição remota. Se o nível de ''multithreading'' for alto o suficiente, ''várias ''threads'' podem ocultar a latência de todas as requisições remotas que não seja o overhead do sistema operacional local. Além disso, essa proposta separa o ''paralelismo físico'' do ''paralelismo lógico''. Especificar o número de ''threads'' de uma aplicação independente de qualquer arquitetura em particular possui várias vantagens, dentre as quais:
 
* independência da arquitetura;
Linha 303:
* facilidade de geração de código por compiladores paralelizadores.
 
Contudo também existem desvantagens com o uso de ''multithreading'', a maioria delas relacionadas ao custo de tratar ''threads'' adicionais.
 
----
Linha 310:
=== Alguns Exemplos de Compiladores Vetorizadores e/ou Paralelizadores ===
 
Nessa seção serão apresentados alguns exemplos de compiladores, falando sobre a arquitetura a qual se destinam, a linguagem tratada, bem como sobre suas peculiaridades. Tais compiladores são diretamente influenciados pelas características da arquitetura a qual se destina. Para as arquiteturas em que é possível, os compiladores aplicam técnicas de paralelização e vetorização conjuntamente para obter o melhor aproveitamento do hardware disponível. Embora as técnicas tenham sido apresentadas separadamente neste texto, nada impede sua aplicação conjunta quando a arquitetura as suporta.
----
[[Imagem:Attention_niels_epting.svg|right|25px]]
[[Imagem:Attention_niels_epting.svg|left|25px]]
<center>
'''Esta secção se encontra em construção''' &nbsp;&nbsp; -&nbsp;&nbsp;
Previsão de Conclusão: ''14/12''<br>
<tt>Progresso:</tt> '''FALTA REVISAR: CONTEÚDO E FORMATAÇÃO'''.
</center>
----
 
Nessa seção serão apresentados alguns exemplos de compiladores, falando sobre a arquitetura a qual se destinam, a linguagem tratada bem como suas peculiaridades. Tais compiladores são diretamente influenciados pelas características da arquitetura a qual se destina. Para as arquiteturas em que é possível, os compiladores aplicam técnicas de paralelização e vetorização conjuntamente para obter o melhor aproveitamento do hardware disponível. Embora as técnicas tenham sido apresentadas separadamente neste texto, nada impede sua aplicação conjunta quando a arquitetura as suporta.
 
----
Linha 333 ⟶ 323:
Previsão de Conclusão: ''14/12''<br>
<tt>Progresso:</tt> <br>
'''FALTA REVISAR:INSERIR CONTEÚDOIMAGENS E01 FORMATAÇÃOe 02'''<br>
'''FALTA INSERIR IMAGENS'''
</center>
----
 
O '''Oxygen [6]''' é um compilador paralelizador que gera código paralelo a partir de códigos em Fortran 77. Para tanto são utilizadas diretivas de compilação que permitem a distribuição de código e dados e suporte a um espaçamento de nomes globais. Os códigos gerados pelo ''Oxygen'' são destinados a arquiteturas paralelas com memória distribuída ou supercomputadores. O modelo de máquina assumido pelo ''Oxygen'' é um torus bi-dimensional de elementos processadores (PE – Processing Elements). Arquiteturas como Parystec Supercluster SC256, iWarp, o Fujitsu AP1OOO, e simulador K9 implementam este tipo de topologia. A figura'''Figura 1''' mostra um modelo de máquina torus bi-dimensional onde cada elemento processador comunica-se através de primitivas ''send'' e ''receive''.
 
----
Linha 348 ⟶ 337:
----
 
O ''Oxygen'' portou dois tipos de sistemas com memória distribuída: o de ''comunicação sistólica'' e o de ''comunicação por memória'', conforme observado na figura. Na '''comunicação sistólica''' não há trocas de mensagens. Os dados são transmitidos do enviador para o receptor usando transferências entre as filas de memória ou filas de registradores existentes entre os PEs. Nesse caso, um PE pode se comunicar com seus quatro vizinhos mais próximos. A '''comunicação por memória''', também chamada '''comunicação por troca de mensagens''', acontece entre primitivas ''send'' e ''receive''. As mensagens podem ser roteadas não só aos vizinhos mais próximos e sim a todos os PEs presentes no torus. Embora as trocas de mensagens sejam mais confortáveis para programar, o tempo gasto no gerenciamento e na computação do roteamento impõem uma maior latência para a comunicação.
Para o compilador Oxygen, os programas Fortran podem ser decompostos em uma seqüência de blocos, os quais podem executar em paralelo em todos os PEs. Esses blocos podem ser locais ou públicos. Blocos locais podem ou não serem executados em paralelo, mas em qualquer caso sua computação é local sem a necessidade de comunicação. Blocos públicos sempre executam em paralelo e com comunicações porque suas operações são sobre estruturas de dados distribuidamente alocadas pelos PEs.
Para o compilador ''Oxygen'', os programas Fortran podem ser decompostos em uma seqüência de blocos, os quais podem executar em paralelo em todos os PEs. Esses blocos podem ser ''locais'' ou ''públicos''. '''Blocos locais''' podem ou não serem executados em paralelo, mas em qualquer caso sua computação é local sem a necessidade de comunicação. '''Blocos públicos''' sempre executam em paralelo e com comunicações porque suas operações são sobre estruturas de dados distribuidamente alocadas pelos PEs.

A figura'''Figura 2''' mostra o modelo de programa para o ''Oxygen''. A coluna da esquerda mostra a decomposição de um código sequencial, através de diretivas de compilação, em blocos. A coluna central mostra a estrutura de código gerada pelo ''Oxygen'' comum a todos os PEs. Cada bloco público é dividido em um tratador de símbolos e um executor, como mostrado na coluna da direita. O tratador de símbolos é uma sequência de análises de consistência de dados e fases de roteamento para descobrir quem são os donos das variáveis compartilhadas. O executor é uma sequência de fases de computação ligadas por pontos de verificação (comunicação). A análise de consistência do tratador de símbolos constrói as estruturas de dados que serão usadas pelo executor. Os pontos de verificação separam os blocos de computação locais dentro do executor fazendo a sincronização dentro do PE e não uma sincronização de toda a máquina. Sempre em que faltarem dados localmente o compilador insere uma primitiva de comunicação para obtê-los.
 
----
Linha 360 ⟶ 351:
----
 
Para os programas que apresentam laços de repetição, o tratamento de seus índices acontece similarmente ao tratamento de índices de vetores. Ambos os índices são particionados e mapeados usando as mesmas diretivas. O ''Oxygen'' possui duas diretivas de mapeamento, ROWWISE e COLWISE, que podem ser usadas nesse caso. Elas tanto podem mapear índices double aninhando laços no torus, ou então,quanto mapear um único laço em todas as linhas (ou colunas) de PEs. Para o último caso, todos os PEs de uma mesma linha (ou coluna) realizam a computação para um mesmo índice.
 
Como exemplo tem-se o código abaixo.
 
<pre>
LOOP SPLIT ROWWISE
do 10 i=1,n
Linha 370 ⟶ 363:
20 continue
10 continue
</pre>
 
No exemplo acima, os dois laços foram alinhado no torus. O laço mais externo foi mapeado entre os PEs de uma mesma coluna do torus pela diretiva ROWWISE considerando que ''n'' é igual ao número de PEs presentes numa coluna do torus. Através da COLWISE, o laço interno foi mapeado entre todos os PEs de uma mesma coluna, novamente considerando ''m'' igual ao número de PEs de uma coluna do torus.
 
----