Você está na página 1de 3

Introdução

A execução simultânea de instruções possibilita ganhos no tempo final de execução e o


melhor aproveitamento das potencialidades das arquiteturas em que executam. Em
particular, de modo geral, apenas parte do conjunto de instruções de um programa merece
atenção quanto à possibilidade de paralelização.

A proposta de exploração do paralelismo implícito procura manter a sintaxe da codificação


seqüencial. Nesta primeira proposta o compilador traduz a aplicação escrita em linguagem
de alto nível seqüencial para sua forma paralela. Na segunda proposta, exploração da
distribuição/paralelismo explícito, as linguagens existentes são ampliadas com
construtores específicos para o paralelismo, ou são criadas novas que disponibilizem os
mesmos.

O desenvolvimento de programas capazes de realizar execuções em paralelo pode ser


obtido de duas maneiras. Uma delas, paralelismo explícito, ocorre quando o paralelismo
fica a cargo do programador, que sabe construir programas paralelos e faz uso de
linguagens e ferramentas de programação que lhe oferecem suporte. Nessa proposta o
programador é responsável por especificar o que pode/deve ser executado em paralelo,
exigindo que, além de dominar o algoritmo, o programador conheça as características
operacionais da arquitetura paralela. Além disso, nesse caso, o projeto do compilador
paralelizador tem sua complexidade reduzida, porém ainda engloba aspectos mais
sofisticados que o projeto de compiladores para códigos seqüenciais. Outra forma,
paralelismo implícito, faz uso de compiladores que detectam o paralelismo existente em
um código seqüencial gerando código paralelo automaticamente. Fica deste modo oculto
ao programador o acréscimo de complexidade introduzido pelo paralelismo, mantendo-se
a sintaxe da codificação seqüencial, ganhando, porém grande complexidade a tarefa de
elaboração do compilador paralelizador.
Compiladores

Um compilador é um programa que, a partir de um código escrito em uma linguagem, o


código fonte, cria um programa semanticamente equivalente, porém escrito em outra
linguagem, código objeto.

Normalmente, o código fonte é escrito em uma linguagem de programação de alto nível,


com grande capacidade de abstração, e o código objeto é escrito em uma linguagem de
baixo nível, como uma seqüência de instruções a ser executada por uma determinada
arquitetura de hardware. O processo de compilação é composto de análise e síntese. A
análise tem como objetivo entender o código fonte e representá-lo em uma estrutura
intermediária. A síntese constrói o código objeto a partir desta representação intermediária.

A análise pode ser subdividida ainda em análise léxica, análise sintática e análise
semântica. A síntese é mais variada, podendo ser composta pelas etapas de geração de
código intermediário, optimização de código e geração de código final (ou código de
máquina), e somente esta última etapa é obrigatória.

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.
Compiladores versus paralelização

As linguagens existentes para programação paralela podem ser divididas em declarativas


e imperativas.

Com as declarativas (lógicas e funcionais), obtém-se um paralelismo de granulação fina e


que pode ser explorado mais naturalmente por compiladores específicos. As linguagens de
fluxo de dados (funcionais) seguem essa característica. Exemplos dessas linguagens são
Sisal, Val e Haskiel.

Nas linguagens imperativas, algumas possibilidades estão disponíveis:

    Compiladores paralelizadores: geram automaticamente versões paralelas de programas


não paralelos. Esses compiladores exigem o mínimo de trabalho de conhecimento extra
por parte do usuário, mas o desempenho obtido normalmente é modesto (embora existam
aplicações que se adequem bem). Também, nem sempre esses compiladores estão
disponíveis, principalmente para sistemas de memória distribuída, além de apresentarem
baixa flexibilidade. Normalmente exploram granularidade fina. Como exemplo temos:
Oxygen, OSCAR, PARADIGM, SUIF, Parafrase2 e Cray.

    Extensões paralelas: são bibliotecas cujo conjunto de instruções, aliado com as
primitivas da linguagem hospedeira, permite o desenvolvimento de aplicações paralelas.
Possuem como vantagem o fato de não exigirem do usuário o aprendizado de uma nova
linguagem. Normalmente apresentam um desempenho melhor quando comparadas com
os compiladores paralelizadores. Como exemplo tem-se PVM (Parallel Virtual Machine),
MPI (Message Passing Interface, para arquiteturas de memória distribuída) e Parmacs;

    Compiladores especiais: permitem a extensão de linguagens seqüenciais com


ferramentas para o desenvolvimento de aplicações paralelas, como processos e
monitores. Como exemplo pode-se citar Pascal Concorrente (para arquiteturas de
memória compartilhada);

    Linguagens concorrentes: são linguagens criadas especificamente para esse fim.
Apresentam a desvantagem de exigir do usuário o aprendizado de uma nova linguagem de
programação, além de possuírem baixa portabilidade. Por outro lado, podem oferecer:
        Flexibilidade;
        Clareza para ativação e coordenação de processos;
        Ferramentas para depuração e detecção de erros;
        Melhor desempenho.

Como exemplos, podem ser citadas as linguagens Occam, Ada, HPF e C*.
Compiladores paralelizadores

Para que seja possível detectar o paralelismo implícito em uma seqüência de instruções,
faz-se necessária uma análise das dependências de dados entre as instruções. Esse é um
dos maiores desafios para o desenvolvimento de compiladores paralelizadores eficientes.

Se não houver dependências de dados, as instruções podem ser executadas ao mesmo


tempo. Caso contrário, conforme o tipo de dependência existente, o compilador poderá
realizar otimizações capazes de melhorar o desempenho final do programa, por exemplo,
realizando rearranjos nos índices dos loops ou retirando do interior de loops operações
que não necessitam estar ali (nesse caso, podendo ser realizadas anteriores ou
posteriores ao loop). Os laços de repetição presentes em códigos seqüenciais são
potenciais fontes de paralelismo e são foco das análises de compiladores.

A tarefa de paralelização pode ser dividia em três subproblemas:

    Identificação de regiões com potencial de paralelismo;


    Mapeamento do paralelismo dentro da arquitetura da máquina alvo;
    Geração e otimização de código paralelo.

Quando os pontos de paralelismo já foram identificados pelo programador, as tarefas de


mapeamento e geração de código necessitam de uma detalhada análise do compilador e
de transformações no código.

A eficiência dos programas gerados por esse tipo de compiladores depende diretamente
da arquitetura da máquina sobre a qual ele será executado. Existem compiladores para
arquiteturas vetoriais que detectam instruções de laços de repetição que podem ser
transformadas em instruções vetoriais. Esse tipo de compilador é conhecido como
compilador vetorizador. Em arquiteturas vetoriais as operações são executadas em
pipeline. Cada CPU (Central Processing Unit) vetorial está associada a um tamanho de
vetor específico que indica o número máximo de elementos que podem ser inseridos no
pipeline. Um compilador vetorizador identifica instruções de laços de repetição que podem
ser convertidas em instruções vetoriais. Quanto maior o número de laços convertidos em
instruções vetoriais, maior será o desempenho do programa sobre a arquitetura. Quando
se trata de arquiteturas multiprocessadas, os compiladores paralelizadores particionam o
conjunto de instruções de um laço entre os processadores da arquitetura para sua
execução concorrente. Para os casos em que as arquiteturas suportam, podem vir a ser
empregadas tanto técnicas de paralelização quanto vetorização conjuntamente.

Alguns exemplos de compiladores paralelizadores/vetorizadores são o Oxigen, OSCAR e


PARADIGM. Esses compiladores geram código paralelo a partir de códigos seqüenciais
escritos em Fortran, assim como vários outros compiladores paralelizadores em
desenvolvimento. Um outro exemplo é o SUIF que paraleliza códigos seqüenciais Fortran
e C. O Parafrase2 é um outro exemplo de compilador paralelizador para a linguagem C.

Linguagem C é uma derivadora para Java, C++, C# e outras baseadas em sua estrutura.

Última atualização: terça, 24 mar 2020, 22:43