Relatório do Trabalho de Conclusão de Curso Curso de Ciência da Computação

Texto

(1)

Relat´ orio do Trabalho de Conclus˜ ao de Curso

Curso de Ciˆencia da Computa¸c˜ ao

Compila¸c˜ ao de c´ odigo C/MPI para C/Pthreads

Diego Francisco de Gastal Morales Aluno

Nicolas Maillard Orientador

Porto Alegre, novembro de 2008

(2)

LISTA DE FIGURAS . . . . 4

LISTAGENS . . . . 5

1 INTRODU ¸ C ˜ AO . . . . 6

2 CONTEXTO CIENT´ıFICO . . . . 8

2.1 Motiva¸ c˜ ao . . . . 8

2.2 Objetivo . . . . 9

2.3 Revis˜ ao bibliogr´ afica e trabalhos relacionados . . . . 9

3 PROJETO . . . . 12

3.1 Ferramentas para a constru¸ c˜ ao de compiladores . . . . 12

3.2 Premissas e simplifica¸ c˜ oes . . . . 14

3.3 Arquitetura do M2PI . . . . 15

4 INTERFACES DE PROGRAMA¸ C ˜ AO PARALELA . . . . 16

4.1 MPI: Message Passing Interface . . . . 16

4.1.1 Inicializa¸c˜ ao e Finaliza¸c˜ ao . . . . 16

4.1.2 Ranks, grupos, e comunicadores . . . . 17

4.1.3 Comunica¸c˜ ao ponto-a-ponto . . . . 17

4.1.4 Comunica¸c˜ ao coletiva . . . . 18

4.2 Posix Threads . . . . 18

4.2.1 Cria¸c˜ ao e jun¸c˜ ao . . . . 19

4.2.2 Sincroniza¸c˜ ao . . . . 20

4.2.3 Thread-Specific Data . . . . 22

4.3 Thread Local Storage . . . . 22

5 ANTLR: COMPILADOR DE COMPILADORES . . . . 24

5.1 Regras . . . . 24

5.2 Reescrita do fluxo de tokens e Modelos . . . . 25

5.3 Escopos . . . . 27

6 IMPLEMENTA¸ C ˜ AO . . . . 28

6.1 Reconhecimento das chamadas de fun¸ c˜ oes MPI . . . . 28

6.2 Primeiro programa: Hello World em MPI . . . . 30

6.2.1 Threads em lugar de processos . . . . 30

6.2.2 Ranks e o comunicador global . . . . 32

6.3 Tratando vari´ aveis globais e locais est´ aticas . . . . 33

(3)

6.5 Comunica¸ c˜ ao coletiva: MPI Bcast . . . . 38

6.6 Reflex˜ ao sobre a implementa¸ c˜ ao . . . . 39

7 AVALIA¸ C ˜ AO DE DESEMPENHO . . . . 40

7.1 Ambiente e metodologia de testes . . . . 40

7.2 Programas testados . . . . 41

7.3 Resultados . . . . 43

8 CONSIDERA¸ C ˜ OES FINAIS . . . . 48

REFERˆ ENCIAS . . . . 50

APˆ ENDICE A LISTAGEM COMPLETA DO HELLO-WORLD MPI COM- PILADO PELO M2PI . . . . 52

APˆ ENDICE B LISTAGEM COMPLETA DO PROGRAMA DE TESTE

DE VARI ´ AVEIS GLOBAIS E LOCAIS EST´ ATICAS COM-

PILADO PELO M2PI . . . . 54

(4)

Figura 3.1: ANTLRWorks durante uma sess˜ ao de depura¸c˜ ao do M2PI . . . . 13

Figura 3.2: Vis˜ ao geral da compila¸c˜ ao de um programa C/MPI usando o M2PI 15 Figura 6.1: Recebimento iniciado antes do envio . . . . 37

Figura 6.2: Envio iniciado antes do recebimento . . . . 38

Figura 6.3: Fun¸c˜ ao MPI Bcast sendo executada por trˆ es threads . . . . 39

Figura 7.1: Ping-Pong variando o tamanho das mensagens . . . . 43

Figura 7.2: Mestre-escravo variando o n´ umero de processos . . . . 44

Figura 7.3: CE variando o n´ umero de mensagens . . . . 45

Figura 7.4: CE variando o n´ umero de processos . . . . 45

Figura 7.5: Mestre-escravo variando o tamanho da mensagem . . . . 46

(5)

6.1 Reconhecimento das chamadas de fun¸c˜ ao MPI . . . . 29

6.2 Reconhecimento das chamadas MPI_Init e MPI_Recv . . . . 30

6.3 Hello World usando MPI . . . . 30

6.4 Indentifica¸c˜ ao do c´ odigo das threads . . . . 31

6.5 Fun¸c˜ ao main de um programa transformado pelo M2PI . . . . 32

6.6 Estrutura de um comunicador no M2PI . . . . 32

6.7 Inser¸c˜ ao da palavra-chave thread na declara¸c˜ ao de vari´ aveis globais 33 6.8 Rastreamento do escopo global na gram´ atica do M2PI . . . . 33

6.9 Inser¸c˜ ao da palavra-chave thread na declara¸c˜ ao de vari´ aveis est´ ati- cas locais . . . . 34

6.10 Trecho de programa usando vari´ aveis globais e est´ aticas . . . . 35

6.11 Vari´ aveis globais e est´ aticas ajustadas para usar TLS . . . . 35

A.1 Hello-World MPI compilado pelo M2PI . . . . 52

B.1 Programa dos contadores local, global e local est´ atico, compilado pelo

M2PI . . . . 54

(6)

1 INTRODU ¸ C ˜ AO

Este Trabalho de Conclus˜ ao de Curso (TCC) estuda uma alternativa para a otimiza¸c˜ ao de desempenho de programas escritos usando a norma MPI (Message Passing Interface) em computadores multiprocessados.

Com o surgimento de processadores multicore, arquiteturas paralelas de hardware est˜ ao se popularizando e alcan¸cando todos computadores, mesmo os pessoais e os port´ ateis. Essa nova tecnologia aumenta a demanda de programas paralelos, que possam aproveitar toda sua capacidade, e conseq¨ uentemente aumenta tamb´ em a demanda por novas ferramentas e t´ ecnicas que auxiliem no desenvolvimento destes programas.

Existem v´ arias alternativas para ganhar desempenho com a adi¸c˜ ao de paralelismo

`

as aplica¸c˜ oes. Al´ em do uso direto de Threads ou do MPI, existem solu¸c˜ oes como o OpenMP

1

e o Cilk++

2

, que buscam paralelizar programas escritos sequencialmente atrav´ es de pequenas indica¸c˜ oes fornecidas pelo desenvolvedor. Ou ainda outras abor- dagens como a do UPC

3

, que busca abstrair do programador a execu¸c˜ ao distribu´ıda de um programa, fornecendo uma ilus˜ ao de mem´ oria compartilhada entre todo o sistema.

Por´ em essas alternativas em geral falham em prover o desempenho esperado por tr´ as das abstra¸c˜ oes, e tentativas de paraleliza¸c˜ ao autom´ atica n˜ ao conseguem muito mais do que paralelizar la¸cos. N˜ ao existe solu¸c˜ ao universal para a obten¸c˜ ao de de- sempenho em programas paralelos. Mas existem muito programas MPI escritos para ambientes distribu´ıdos que poderiam aproveitar melhor as arquiteturas de hardware paralelas. Vers˜ oes de bibliotecas MPI que aproveitam a mem´ oria compartilhada j´ a existem h´ a bastante tempo, mas pouco foi explorado a respeito de uma outra possi- bilidade interessante: a transforma¸c˜ ao est´ atica do c´ odigo MPI em uma alternativa de maior desempenho nessas arquiteturas, atrav´ es de um compilador.

A alternativa estudada neste TCC consiste na utiliza¸c˜ ao de t´ ecnicas e ferramentas de compiladores para realizar a transforma¸c˜ ao de c´ odigo C usando MPI para um c´ odigo C que utiliza threads ao inv´ es de processos, e que realiza toda comunica¸c˜ ao entre as threads pelo acesso compartilhado ` a mem´ oria. Com estas t´ ecnicas, sup˜ oe-se que poderosas altera¸c˜ oes possam ser realizadas no c´ odigo destes programas, e novas oportunidades de otimiza¸c˜ ao possam ser exploradas.

Para para testar essa h´ıpotese, um compilador simples para esta tarefa ´ e con- stru´ıdo, batizado de M2PI (MPI to Pthreads Interface). Seu desempenho ´ e testado frente a outras implementa¸c˜ oes da norma MPI, atrav´ es de uma s´ erie de programas

1

http://openmp.org/wp/

2

http://www.cilk.com

3

http://upc.lbl.gov/

(7)

de testes.

O restante desta monografia est´ a organizada da seguinte forma: o contexto cien- t´ıfico do trabalho ´ e discutido no cap´ıtulo 2, expondo os fatores que o motivam, o objetivo tra¸cado, e a revis˜ ao bibliogr´ afica e dos trabalhos relacionados. O cap´ı- tulo 3 apresenta uma discuss˜ ao sobre a ferramenta utilizada para implementar o compilador, as premissas e simplifica¸c˜ oes adotadas, e a arquitetura geral do M2PI.

Os cap´ıtulos 4 e 5 introduzem conceitos e ferramentas necess´ arios para a com- preens˜ ao da implementa¸c˜ ao do trabalho, descrita detalhadamente no cap´ıtulo 6. Os resultados obtidos s˜ ao discutidos no cap´ıtulo 7, exibindo gr´ aficos de desempenho, e as descri¸c˜ oes da metodologia utilizada e dos programas utilizados para teste.

O relat´ orio ´ e completado pelas considera¸c˜ oes finais do cap´ıtulo 8. Os apˆ endices

A e B listam o c´ odigo fonte transformado de dois programas simples, para que o

leitor possa visualizar como ´ e alterado um programa processado pelo M2PI.

(8)

2 CONTEXTO CIENT´IFICO

2.1 Motiva¸ c˜ ao

Com a populariza¸c˜ ao de arquiteturas de hardware paralelas, capitaneada pelo surgimento dos processadores multicore, cresce cada vez mais a importˆ ancia da pro- grama¸c˜ ao concorrente, necess´ aria para o completo aproveitamento dos m´ ultiplos processadores que agora s˜ ao encontrados at´ e em computadores pessoais e port´ ateis.

Os m´ etodos de realizar esse tipo de programa¸c˜ ao, as ferramentas atualmente uti- lizadas, e at´ e mesmo as formaliza¸c˜ oes te´ oricas nessa ´ area est˜ ao ainda longe de ser consenso, seja na ind´ ustria, seja na academia.

Nas arquiteturas paralelas de hardware mais comuns atualmente, que seguem um modelo de mem´ oria compartilhada uniformemente acess´ıvel, a abordagem predom- inante de programa¸c˜ ao paralela ´ e o uso de threads. Essa abordagem ´ e geralmente aceita como sendo a de melhor desempenho, muito em raz˜ ao de que programas us- ando threads podem aproveitar completamente o modelo compartilhado de acesso a mem´ oria usado pelo hardware. No entanto, justamente por esse modelo – que traz muito indeterminismo ` a programa¸c˜ ao – alguns pesquisadores da ´ area argumentam que a programa¸c˜ ao com threads ´ e demasiadamente dif´ıcil, e conseq¨ uentemente muito propensa a erros, mesmo para profissionais altamente capacitados e experientes (ver por exemplo o artigo The Problem With Threads, de LEE (2006)).

O modelo de troca de mensagens, em que se baseiam bibliotecas como a MPI, ´ e uma op¸c˜ ao mais simples e confi´ avel, mas normalmente tem desempenho inferior ao das solu¸c˜ oes que usam threads diretamente.

Uma alternativa interessante, que busca o melhor dos dois mundos, ´ e manter o modelo de troca de mensagens para o programador, atrav´ es da interface de progra- ma¸c˜ ao, mas automaticamente gerar o c´ odigo execut´ avel de uma forma mais ade- quada ` a arquitetura do hardware alvo. Algumas implementa¸c˜ oes de MPI j´ a buscam algo nesse sentido, fazendo com que o seu ambiente de execu¸c˜ ao (runtime ) realize a comunica¸c˜ ao entre os processos MPI via mem´ oria compartilhada (ao inv´ es de sockets TCP) quando estes processos est˜ ao na mesma m´ aquina.

E poss´ıvel implementar a alternativa acima transformando o c´ ´ odigo gerado pelo

programador, em um processo de compila¸c˜ ao. Tal compila¸c˜ ao poderia buscar uma

s´ erie de otimiza¸c˜ oes que o ambiente de execu¸c˜ ao do MPI n˜ ao poderia realizar. Essa

possibilidade, e o desafio t´ ecnico de implementar tal compilador, s˜ ao as motiva¸c˜ oes

deste trabalho.

(9)

2.2 Objetivo

O objetivo deste trabalho de gradua¸c˜ ao ´ e ser um estudo preliminar sobre a vi- abilidade e utilidade da implementa¸c˜ ao de um compilador que transforme c´ odigo fonte C/MPI em c´ odigo C/pthreads (POSIX threads), onde cada processo criado pelo MPI seja uma thread, e toda comunica¸c˜ ao entre os processos/threads rodando na mesma m´ aquina seja feita atrav´ es da mem´ oria compartilhada.

Para isso, ser´ a feita a implementa¸c˜ ao de um prot´ otipo simplificado de tal compi- lador, restrito a um subconjunto de instru¸c˜ oes do padr˜ ao MPI, e ent˜ ao ser´ a avaliada sua corre¸c˜ ao e seu desempenho. O escopo tamb´ em se limita ` a execu¸c˜ ao n˜ ao dis- tribu´ıda de programas MPI (caso contr´ ario seria necess´ ario se mesclar comunica¸c˜ ao de rede com a feita atrav´ es da mem´ oria, o que foge do foco deste trabalho).

Como decorrˆ encia desse esfor¸co de planejamento e implementa¸c˜ ao, ´ e esperado para o aluno um grande aprofundamento do conhecimento e da experiˆ encia em programa¸c˜ ao concorrente – em particular no uso de pthreads e MPI – e na constru¸c˜ ao de compiladores. Al´ em disso, a participa¸c˜ ao em um projeto de desenvolvimento dessa complexidade ser´ a uma experiˆ encia extremamente importante.

2.3 Revis˜ ao bibliogr´ afica e trabalhos relacionados

Como foi dito na se¸c˜ ao 2.1, o projeto e a programa¸c˜ ao de sistemas paralelos e distribu´ıdos s˜ ao problemas em aberto para a ciˆ encia da computa¸c˜ ao. Esta situa¸c˜ ao, somada ` as muitas facetas e aplica¸c˜ oes de t´ ecnicas de programa¸c˜ ao concorrente (de- sempenho, distribui¸c˜ ao, tolerˆ ancia a falhas, etc), tem motivado um sem n´ umero de modelos e ferramentas que criados para atender as necessidades da ´ area.

O uso de threads ´ e a op¸c˜ ao mais direta para o aproveitamento de computadores com m´ ultiplas CPUs (Central Processing Units ) e mem´ oria compartilhada, pois seu modelo coincide exatamente com o modelo da arquitetura de hardware destas m´ aquinas. BUTENHOF explica o funcionamento e a utiliza¸c˜ ao das POSIX Threads em seu livro Programming with POSIX Threads (1997). DOWNEY (2008) discute uma extensa lista de problemas e padr˜ oes de sincroniza¸c˜ ao.

O MPI, padr˜ ao de fato da ind´ ustria de computa¸c˜ ao de alto desempenho, adota uma abordagem diferente, a de troca de mensagens, mais adequada a sistemas de mem´ oria distribu´ıda como os clusters onde o MPI ´ e muito utilizado. O padr˜ ao

´ e muito bem apresentado em GROPP; LUSK; SKJELLUM (1994) – vers˜ ao 1.1 do padr˜ ao – e GROPP; LUSK; THAKUR (1999) – vers˜ ao 2.0 –, al´ em do volume duplo de referˆ encia por SNIR et al. (1998). QUINN (2004) tem uma abordagem pr´ atica, apresentando algoritmos paralelos cl´ assicos, e trazendo uma vis˜ ao mais geral da

´

area de programa¸c˜ ao paralela, tamb´ em confrontando e combinando o MPI com o OpenMP

1

.

O OpenMP acresce diretivas de pr´ e-processador ` as linguagens C, C++ e FOR- TRAN para que o desenvolvedor indique trechos de c´ odigo que podem ser paraleliza- dos de forma autom´ atica, usando threads. ´ E um das muitas linguagens/bibliote- cas/frameworks de desenvolvimento fornecem para o programador uma abstra¸c˜ ao maior sobre a implementa¸c˜ ao de threads.

O projeto Cilk (BLUMOFE et al., 1996), do MIT, ´ e outra extens˜ ao da linguagem C, que permite a chamada de fun¸c˜ oes como threads separadas, e a sincroniza¸c˜ ao

1

http://openmp.org/wp/

(10)

delas, de forma simples, elegante e eficiente. Esse projeto derivou um produto comercial, o Cilk++

2

que promete simplificar a programa¸c˜ ao paralela para a grande massa de programadores.

A biblioteca de C++ Threading Building Blocks (TBB)

3

da Intel, ´ e uma alterna- tiva de facilita¸c˜ ao do desenvolvimento com threads, permitindo a defini¸c˜ ao de tarefas de alto n´ıvel para serem executadas de forma concorrente, e a utiliza¸c˜ ao de templates C++ prontos de alguns algoritmos paralelos.

A troca de mensagens ´ e uma das caracter´ısticas mais marcantes da linguagem Erlang

4

, que tem instru¸c˜ oes de envio e recep¸c˜ ao de mensagens fazendo parte do cerne da linguagem. O foco de Erlang no entanto est´ a na constru¸c˜ ao de sistemas distribu´ı- dos e confi´ aveis, com m´ınimo tempo de parada, e n˜ ao na constru¸c˜ ao de sistemas de alto desempenho. O projeto HiPE (High Performance Erlang), que foi integrado a distribui¸c˜ ao livre do Erlang, desenvolveu um compilador Just-In-Time (JIT) para c´ odigo nativo para o Erlang. Algumas medi¸c˜ oes mostram um aumento significativo de desempenho(JOHANSSON; PETTERSSON; SAGONAS, 2000). ARMSTRONG (2007) cont´ em um cap´ıtulo dedicado a programa¸c˜ ao de computadores multicore, en- fatizando a alta escalabilidade que pode ser alcan¸cada por programas feitos em Erlang, caso sejam seguidas algumas boas pr´ aticas durante seu desenvolvimento.

Uma abordagem h´ıbrida ´ e adotada pelo Unified Parallel C (UPC)

5

, que fornece uma ilus˜ ao de mem´ oria compartilhada para o programador, enquanto usa uma combina¸c˜ ao de mem´ oria compartilhada e troca de mensagens para comunica¸c˜ ao entre as threads e processos do sistema.

Um dos primeiros trabalhos a abordar a implementa¸c˜ ao de processos MPI como threads foi o TOMPI(DEMAINE, 1997), com o prop´ osito de ser um ambiente de testes de programas paralelos em um ´ unico computador (com um ou mais proces- sadores). Outro trabalho semelhante ´ e o TMPI (TANG; SHEN; YANG, 1999), buscando diretamente o aumento de desempenho de aplica¸c˜ oes MPI em m´ aquinas de mem´ oria compartilhada e clusters delas.

O MPICH

6

e o Open-MPI

7

s˜ ao as distribui¸c˜ oes MPI de c´ odigo aberto com de- senvolvimento ativo

8

mais difundidas, e as duas tem m´ odulos de comunica¸c˜ ao que utilizam mem´ oria compartilhada. V´ arios fabricantes possuem sua implementa¸c˜ ao propriet´ aria de MPI, e possivelmente todos tem suporte a esta otimiza¸c˜ ao nos seus produtos.

Tanto TOMPI quanto TMPI adotam uma estrat´ egia de tradu¸c˜ ao/pr´ e-processamento do c´ odigo para solucionar o uso de vari´ aveis globais e est´ aticas no programa origi- nal (ver se¸c˜ oes 3.3 e 6.3 para mais detalhes sobre este problema), no entanto n˜ ao ´ e conhecido trabalho que busque tirar outras vantagens de t´ ecnicas de compila¸ c˜ ao, como este.

A referˆ encia canˆ onica de compiladores ´ e o “Livro do Drag˜ ao” (AHO; SETHI;

ULLMAN, 1986). PARR (2007) faz uma introdu¸c˜ ao mais superficial e leve ` a ´ area enquanto apresenta sua ferramenta de gera¸c˜ ao de compiladores. A se¸c˜ ao 3.1 ap-

2

http://www.cilk.com

3

http://www.threadingbuildingblocks.org/

4

http://www.erlang.org/

5

http://upc.lbl.gov/

6

http://www.mcs.anl.gov/research/projects/mpich2/

7

http://www.open-mpi.org/

8

O desenvolvimento da conhecida distribui¸ c˜ ao LAM-MPI est´ a em sua maior parte terminado,

em favor do Open-MPI, onde seus autores est˜ ao focados atualmente.

(11)

resenta algumas ferramentas deste tipo, discute suas principais caracter´ısticas, e

mostra qual foi escolhida para auxiliar a implementa¸c˜ ao do M2PI.

(12)

3 PROJETO

Este cap´ıtulo apresenta algumas premissas e defini¸c˜ oes que formaram uma base para o desenvolvimento deste trabalho. Foram decis˜ oes tomadas na fase inicial, como qual a ferramenta utilizada para criar o compilador (se¸c˜ ao 3.1); quais as sim- plifica¸c˜ oes necess´ arias para manter o trabalho realiz´ avel dentro do tempo dispon´ıvel (se¸c˜ ao 3.2); e qual a arquitetura geral adotada para a implementa¸c˜ ao do M2PI (se¸c˜ ao 3.3).

3.1 Ferramentas para a constru¸ c˜ ao de compiladores

Como o reconhecimento e interpreta¸c˜ ao de c´ odigo fonte ´ e uma tarefa central na implementa¸c˜ ao deste trabalho, foi feita uma pesquisa sobre ferramentas que possam servir de aux´ılio. Essas ferramentas s˜ ao classificadas normalmente sob o r´ otulo de Compiladores de Compiladores (CC), e normalmente se comp˜ oem de um gerador de analisadores l´ exicos – que reconhecem e agrupam sequˆ encias de caracteres em entidades significativas chamadas normalmente de tokens – e de um gerador de analisadores sint´ aticos, ou parsers que reconhecem e tomam a¸c˜ oes sobre um conjunto estruturado de tokens.

Normalmente estas ferramentas lˆ eem arquivos de especifica¸c˜ ao com regras que especificam a gram´ atica (usando nota¸c˜ oes similares a BNF ou EBNF), e os tokens (usando express˜ oes regulares), e permitem que a¸c˜ oes sejam associadas a cada regra.

Estas a¸c˜ oes s˜ ao nada mais que trechos de c´ odigo na mesma linguagem na qual o lexer ou parser ´ e gerado, que v˜ ao ser executadas quando um trecho da entrada do analisador casar com a regra associada.

A dupla mais tradicional e conhecida de lexer e parser ´ e a Lex & Yacc (MA- SON; BROWN, 1990), hoje em dia normalmente representados pelas implemen- ta¸c˜ oes compat´ıveis do projeto GNU

1

, respectivamente o Flex e o Bison. O Yacc suporta gram´ aticas LALR(1), gerando um parser bottom-up em c´ odigo C. Flex e Bison suportam tamb´ em a linguagem C++.

O JavaCC

2

´ e a op¸c˜ ao mais popular para a plataforma java. Ele gera analisadores l´ exicos e parsers top-down LL(k) escritos em Java, e inclui ferramentas para gera¸c˜ ao de ´ arvores de sintaxe, e gera¸c˜ ao autom´ atica de documenta¸c˜ ao a partir dos arquivos de defini¸c˜ ao da gram´ atica.

Algumas das vantagens destacadas pelos seus autores s˜ ao suas ´ otimas facilidades de debug do processo de reconhecimento, mensagens de erro e diagn´ ostico muito

1

http://www.gnu.org

2

https://javacc.dev.java.net/

(13)

Figura 3.1: ANTLRWorks durante uma sess˜ ao de depura¸c˜ ao do M2PI

claras, o suporte completo a Unicode nas gram´ aticas e nos parsers gerados, e a portabilidade associada a plataforma Java.

O ANTLR

3

(ANother Tool for Language Recognition), ´ e uma outra op¸c˜ ao pop- ular na comunidade Java, mas suportando tamb´ em (com variados n´ıveis de comple- tude) C, C++, Python, C#, Objective C, entre outras. Ele gera parsers top-down com um algoritmo LL(*), uma extens˜ ao ao algoritmo LL(k), que permite um n´ıvel de lookahead arbitr´ ario (PARR, 2007).

O ANTLR foi o escolhido, pois apresenta vantagens similares ` as do JavaCC, e algumas outras que s˜ ao bastante ´ uteis para este trabalho. Vantagens como o ANTL- RWorks, uma IDE (Integrated Development Environment ) que possui um interpre- tador e um depurador embutidos (ver figura 3.1), permitindo uma an´ alise detalhada do processo de reconhecimento; e como o seu mecanismo de templates, que separa a l´ ogica de gera¸c˜ ao do texto do seu conte´ udo, e pode facilitar a gera¸c˜ ao de programas que fazem tradu¸c˜ ao de c´ odigo ou geram algum tipo de texto estruturado, como ´ e o caso deste trabalho.

Para mais informa¸c˜ oes sobre o funcionamento de lexers e parsers, algoritmos bottom-up e top-down e as classes de linguagens LALR e LL, ver (AHO; SETHI;

ULLMAN, 1986).

3

http://www.antlr.org/

(14)

3.2 Premissas e simplifica¸ c˜ oes

Para que os objetivos tra¸cados possam ser atingidos em tempo compat´ıvel com a carga hor´ aria prevista para o TCC, diversas simplifica¸c˜ oes s˜ ao necess´ arias. Em particular, ´ e assumido sobre o programa MPI original

4

:

• A quantidade total de mem´ oria utilizada (incluindo todos os processos/threads do programa) cabe no espa¸co de endere¸camento dispon´ıvel para um processo.

• Todos os arquivos abertos simultaneamente podem ser abertos da mesma forma por um processo apenas.

• N˜ ao utiliza bibliotecas ou chamadas de sistema que n˜ ao sejam thread-safe.

• N˜ ao faz uso de threads.

Al´ em dessas premissas, os seguintes itens s˜ ao importantes para a simplifica¸c˜ ao da implementa¸c˜ ao do M2PI:

• O M2PI n˜ ao valida completamente o c´ odigo C que interpreta, deixando que a valida¸c˜ ao completa seja feita s´ o pelo GCC sobre o programa traduzido. A maior desvantagem disso ´ e a dificuldade extra na depura¸c˜ ao de erros de compi- la¸c˜ ao, pois a numera¸c˜ ao das linhas e at´ e o pr´ oprio c´ odigo envolvido no relat´ orio de erros pode ser diferente do original passado pelo usu´ ario ao M2PI.

• Portabilidade n˜ ao ´ e uma preocupa¸c˜ ao primordial deste trabalho. Ele est´ a sendo desenvolvido em uma plataforma Intel 32, com ambiente Ubuntu GNU / Linux de 32 bits.

Tamb´ em ´ e abordado apenas um pequeno subconjunto das fun¸c˜ oes definidas pelo padr˜ ao MPI 1.1, como ´ e detalhado abaixo na se¸c˜ ao 3.2. Por fim, a prova da equiv- alˆ encia entre o programa original e o traduzido n˜ ao faz parte do escopo do trabalho.

Defini¸ c˜ ao do subconjunto MPI a ser tratado

Foram escolhidas para este projeto apenas sete fun¸c˜ oes, dentre as mais b´ asicas e comuns do MPI. N˜ ao obstante, ela s˜ ao uma amostra representativa do padr˜ ao, abrangendo comunica¸c˜ ao ponto-a-ponto e coletiva, e permitem a implementa¸c˜ ao de in´ umeros programas reais feitos com MPI. S˜ ao as seguintes:

• Fun¸c˜ oes B´ asicas: MPI_Init, MPI_Finalize, MPI_Comm_size, MPI_Comm_rank.

• Fun¸c˜ oes de comunica¸c˜ ao ponto-a-ponto: MPI_Send, MPI_Recv.

• Fun¸c˜ oes de comunica¸c˜ ao coletiva: MPI_Bcast.

4

Estes quatro itens abaixo s˜ ao as mesmas premissas de TANG; SHEN; YANG (1999).

(15)

arquivo.c

#include <mpi.h>

M2PI.class

runtime m2pi

GCC arquivo

(executável usando threads)

m2picc

arquivo.c.m2pi.c

#include <pthread.h>

Figura 3.2: Vis˜ ao geral da compila¸c˜ ao de um programa C/MPI usando o M2PI

3.3 Arquitetura do M2PI

Para efetuar a tradu¸c˜ ao de programas C/MPI para C/Pthreads, o M2PI precisa executar trˆ es tarefas distintas, a saber:

Transforma¸ c˜ ao das vari´ aveis globais e est´ aticas: Para preservar, em um con- junto de threads, a semˆ antica de um conjunto de processos MPI que acessam vari´ aveis globais ou locais est´ aticas, ´ e preciso transformar essa vari´ aveis em dados que de alguma forma sejam particulares a cada thread. TANG; SHEN;

YANG (1999) apresentam este problema e suas poss´ıveis solu¸c˜ oes.

Gerenciamento das threads e da infraestrutura de comunica¸ c˜ ao: Gera¸c˜ ao de c´ odigo para criar as N threads (N s´ o ´ e conhecido no momento da execu¸c˜ ao), as estruturas de comunicadores e grupos, entre outras necess´ arias para a co- munica¸c˜ ao entre as threads.

Tradu¸ c˜ ao das fun¸ c˜ oes MPI: A implementa¸c˜ ao das fun¸c˜ oes MPI escolhidas (se¸c˜ ao 3.2) usando os recursos constru´ıdos na tarefa mencionada acima. Cada fun¸c˜ ao

´

e um subproblema diferente. ´ E neste ponto que um ganho pode ser atingido pela abordagem de compila¸c˜ ao usada neste trabalho.

Ap´ os estas tarefas serem completadas, o c´ odigo fonte resultante (com extens˜ ao m2pi.c

5

) deve ser ligado com a biblioteca runtime do M2PI para gerar um execut´ avel, equivalente ao programa original, mas que usa threads ao inv´ es de processos. A figura 3.2 ilustra esse processo. Um script chamado m2picc encapsula estes diferentes passos, fornecendo um processo de compila¸c˜ ao an´ alogo ao que seria feito para o programa MPI.

O compilador M2PI ´ e constru´ıdo em Java usando o ANTLR. A biblioteca runtime

´ e feita em C usando POSIX Threads, e o utilit´ ario mpicc ´ e apenas um script bash simples que invoca o compilador M2PI e compila o c´ odigo C resultante ligando este com a biblioteca. A se¸c˜ ao 6 trata dos detalhes da implementa¸c˜ ao do compilador e da biblioteca. As se¸c˜ oes 4.1, 4.2, 4.3 e 5 servem de suporte ao entendimento dos detalhes da implementa¸c˜ ao, apresentando ferramentas e conceitos que foram utilizados nela.

5

O c´ odigo transformado permanece no sistema de arquivos com esta extens˜ ao, possibilitando a

sua inspe¸ c˜ ao manual.

(16)

4 INTERFACES DE PROGRAMA¸ C ˜ AO PARALELA

Este cap´ıtulo apresenta o que pode ser tido como o formato de entrada e o formato de sa´ıda do compilador M2PI.

O formato de entrada do compilador ´ e um programa C utilizando MPI, uma interface de programa¸c˜ ao paralela baseada em troca de mensagens. Os principais conceitos e fun¸c˜ oes MPI necess´ arios para o acompanhamento da implementa¸c˜ ao deste trabalho s˜ ao apresentados na se¸c˜ ao 4.1.

O formato de sa´ıda do compilador ´ e um programa C utilizando threads atrav´ es da biblioteca Pthreads, apresentada na se¸c˜ ao 4.2. Tamb´ em faz parte da sa´ıda do compilador a utiliza¸c˜ ao de um recurso do compilador GCC chamado Thread Local Storage (TLS), apresentado na se¸c˜ ao 4.3, que permite preservar a semˆ antica do acesso de vari´ aveis globais e locais est´ aticas em um programa que usa Pthreads.

4.1 MPI: Message Passing Interface

O MPI (Message Passing Interface) ´ e uma norma que define uma API (Applica- tion Programming Interface) para a constru¸c˜ ao de programas paralelos usando troca de mensagens. ´ E o padr˜ ao de fato da ind´ ustria de computa¸c˜ ao de alto desempenho, onde sistemas de mem´ oria distribu´ıda baseados em grandes agrupamentos de com- putadores poderosos s˜ ao o caso mais comum, e por isso suporta aos mais variados tipo de redes e mecanismos de interconex˜ ao de computadores. Esta se¸c˜ ao apresenta uma breve introdu¸c˜ ao ` as interfaces definidas no MPI, e aos conceitos e terminologia empregados na norma. Para aprofundar o conhecimento sobre ela, podem ser con- sultados GROPP; LUSK; SKJELLUM (1994), GROPP; LUSK; THAKUR (1999), e SNIR et al. (1998).

Apesar do MPI ser uma especifica¸c˜ ao muito grande, GROPP; LUSK; SKJEL- LUM (1994) aponta que quase qualquer programa MPI pode ser implementado com apenas seis fun¸c˜ oes b´ asicas. O resto da norma fornece na maior parte combina¸c˜ oes e atalhos para idiomas freq¨ uentes que podem constru´ıdos de forma mais complexa com estas primitivas b´ asicas. Estas seis fun¸c˜ oes s˜ ao apresentadas nos t´ opicos 4.1.1, 4.1.2 e 4.1.3 a seguir. A se¸c˜ ao 4.1.4 apresenta uma s´ etima fun¸c˜ ao, um recurso extra associado ` as comunica¸c˜ oes coletivas suportadas pelo MPI.

4.1.1 Inicializa¸ c˜ ao e Finaliza¸ c˜ ao

As fun¸c˜ oes MPI_Init e MPI_Finalize fazem a inicializa¸c˜ ao e finaliza¸c˜ ao da bib- lioteca MPI. Suas assinaturas s˜ ao as seguintes:

int MPI_Init(int *argc, char ***argv)

(17)

int MPI_Finalize()

Quase nenhuma fun¸c˜ ao do MPI pode ser chamada fora do trecho de c´ odigo delim- itado por estas fun¸c˜ oes. A norma tamb´ em n˜ ao especifica como deve ser o ambiente antes ou depois da execu¸c˜ ao delas, nem ao menos quantos processos existir˜ ao nesses momentos. Por isso ´ e recomendado que se fa¸ca o m´ınimo poss´ıvel fora delas, e pro- gramas MPI normalmente come¸cam por MPI_Init e terminam por MPI_Finalize (a menos de um return colocado ap´ os esta ´ ultima).

4.1.2 Ranks, grupos, e comunicadores

Os processos no MPI se dividem em grupos, dentro dos quais cada um tem um n´ umero de identifica¸c˜ ao, o rank. A cada grupo est˜ ao associados um ou mais comu- nicadores, que organizam os processos em uma topologia virtual. O comunicador

´ e a referˆ encia utilizada nas fun¸c˜ oes de comunica¸c˜ ao, indicando quais processos es- t˜ ao relacionados com a opera¸c˜ ao, e em que ordem eles tomam parte nela, quando necess´ ario.

Ao inicializar uma programa MPI, ser´ a criado um comunicador global, chamado MPI_COMM_WORLD, contendo todos os processos que foram disparados.

Entre as seis fun¸c˜ oes b´ asicas mencionadas neste cap´ıtulo, duas delas inspecionam comunicadores para descobrir quantos processos est˜ ao agrupados no comunicador, e qual o seu rank entre eles. S˜ ao respectivamente as fun¸c˜ oes MPI_Comm_size e MPI_Comm_rank, cujas assinaturas s˜ ao exibidas abaixo:

int MPI_Comm_size(MPI_Comm comm, int *size) int MPI_Comm_rank(MPI_Comm comm, int *rank)

As duas fun¸c˜ oes recebem um comunicador comm, e um ponteiro para um inteiro onde a informa¸c˜ ao requisitada ser´ a armazenada. O valor retornado por elas, como por todas fun¸c˜ oes MPI (` a exce¸c˜ ao de duas que tratam da contagem de tempo) ´ e uma sinaliza¸c˜ ao de sucesso ou insucesso na execu¸c˜ ao da opera¸c˜ ao.

4.1.3 Comunica¸ c˜ ao ponto-a-ponto

As ´ ultimas duas das seis fun¸c˜ oes b´ asicas s˜ ao a MPI_Send e MPI_Recv, que per- mitem a comunica¸c˜ ao entre os processos. Esta comunica¸c˜ ao nada mais ´ e do que a transferˆ encia de uma s´ erie de bytes de um processo para outro processo. Suas assinaturas s˜ ao as seguintes:

int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)

int MPI_Recv( void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status ) O significado de cada parˆ ametro ´ e explicado na lista abaixo:

buf: Indica a ´ area de mem´ oria que ´ e a origem ou destino dos bytes.

count: Quantidade de elementos transferidos na mensagem.

(18)

datatype: Tipo dos dados transferidos na mensagem, normalmente indicado por constantes MPI_INT, MPI_FLOAT, MPI_CHAR, etc.

source/dest: Os ranks de origem/destino da mensagem, sendo que a origem pode ser especificada como a constante MPI_ANY_SOURCE, indicando que mensagens de qualquer destino devem ser recebidas por essa chamada.

tag: Chamadas MPI_Recv podem especificar que desejam receber apenas mensagens marcadas com um n´ umero espec´ıfico, o tag, atuando este como um filtro.

comm: O comunicador utilizado para transferˆ encia da mensagem.

status: A estrutura apontada por este parˆ ametro recebe informa¸c˜ oes sobre a men- sagem recebida, como por exemplo sua origem, importante informa¸c˜ ao quando MPI_ANY_SOURCE ´ e utilizado.

Al´ em disso, vale notar que a opera¸c˜ ao de recebimento ´ e bloqueante, e a norma MPI institui que o MPI_Send pode bloquear at´ e que seja feito o recebimento da men- sagem. O MPI suporta outros modos de comunica¸c˜ ao, incluindo um n˜ ao-bloqueante atrav´ es de fun¸c˜ oes MPI_ISend e MPI_IRecv, mas estes modos adicionais n˜ ao s˜ ao abordados neste trabalho.

4.1.4 Comunica¸ c˜ ao coletiva

Entre as muitas facilidades de comunica¸c˜ ao providas pela norma MPI, uma delas

´ e um conjunto de fun¸c˜ oes de comunica¸c˜ ao coletiva. A mais b´ asica delas ´ e a fun¸c˜ ao de nome MPI_Bcast, que faz a difus˜ ao de dados. Sua assinatura ´ e apresentada abaixo:

int MPI_Bcast (void *buf, int count, MPI_Datatype datatype, int root, MPI_Comm comm )

O significado dos parˆ ametros ´ e similar ao das chamadas de comunica¸c˜ ao ponto-a- ponto, mas esta fun¸c˜ ao indica adicionalmente a raiz da difus˜ ao, o rank especificado pelo parˆ ametro root. O MPI_Bcast ´ e chamado por todos os processos que fazem parte de um grupo comunicador. Ap´ os o retorno da chamada, o buffer do processo raiz foi copiado para o buffer de cada outro processo do grupo.

Existem v´ arias outras fun¸c˜ oes de comunica¸c˜ ao coletiva, que fazem outras formas de distribui¸c˜ ao dos dados, fazem a redu¸c˜ ao de resultados usando v´ arias opera¸c˜ oes aritm´ eticas, entre outros recursos. Mas a MPI_Bcast ´ e a ´ unica abordada neste trabalho, sendo implementada pelo M2PI como uma funcionalidade extra, al´ em das outras seis fun¸c˜ oes b´ asicas discutidas anteriormente.

4.2 Posix Threads

POSIX (Portable Operating System Interface) ´ e o nome de uma cole¸c˜ ao de padr˜ oes relacionados, especificados pelo IEEE

1

(Institute of Electrical and Elec- tronics Engineers), que definem diversas interfaces de programa¸c˜ ao e utiliza¸c˜ ao de sistemas operacionais. Um destes padr˜ oes ´ e o POSIX Threads, ou Pthreads, que define uma API para o desenvolvimento de aplica¸c˜ oes com m´ ultiplas threads.

1

http://www.ieee.org

(19)

Esta se¸c˜ ao apresenta uma vis˜ ao geral sobre as interfaces definidas no padr˜ ao e sobre os conceitos subjacentes. Uma abordagem completa sobre o Pthreads ´ e dada em BUTENHOF (1997).

Uma thread ´ e um contexto de execu¸c˜ ao; um fluxo, ou “linha” (a tradu¸c˜ ao literal de thread ), de execu¸c˜ ao dentro de um processo em um sistema operacional (SO).

Um processo com v´ arias “linhas” de execu¸c˜ ao distintas e concorrentes ´ e chamado de um processo multithreaded, podendo assim executar diferentes tarefas de forma as´ın- crona e independente. Enquanto uma tarefa fica bloqueada, outras continuam sua execu¸c˜ ao, evitando que o processo fique “parado”. Al´ em disso, todas threads compar- tilham a mem´ oria alocada para o processo, o que fornece um meio de comunica¸c˜ ao extremamente r´ apido e direto.

Adicionalmente, dependendo da forma de implementa¸c˜ ao do sistema de threads, elas permitem que as v´ arias unidades de processamento de uma m´ aquina multipro- cessada sejam aproveitadas por um mesmo processo. Para isso ´ e necess´ ario que as threads sejam implementadas em n´ıvel de sistema, com suporte do hardware e do sis- tema operacional (o kernel enxerga e manipula cada thread individualmente). Mas threads podem ser tamb´ em gerenciadas por c´ odigo executando completamente no es- pa¸co de usu´ ario do SO (ditas ent˜ ao de n´ıvel de usu´ ario), sem nenhum conhecimento delas no kernel. Modelos h´ıbridos (usu´ ario/sistema) tamb´ em s˜ ao utilizados.

Neste trabalho consideramos uma implementa¸c˜ ao em n´ıvel de sistema, n˜ ao h´ıbrida, a mais comumente usada nas plataformas Linux atuais. Informa¸c˜ oes sobre as difer- en¸cas, vantagens e desvantagens destas op¸c˜ oes podem ser encontradas em TOSCANI;

OLIVEIRA; CAR´ıSSIMI (2003), TANENBAUM (2001), ou BUTENHOF (1997).

A norma POSIX, como trata da interface de programa¸c˜ ao, ´ e independente da op¸c˜ ao de implementa¸c˜ ao do sistema de threads. Ela define as assinaturas e a semˆ an- tica de uma s´ eries de fun¸c˜ oes para cria¸c˜ ao, destrui¸c˜ ao, manuten¸c˜ ao, espera e comu- nica¸c˜ ao entre threads. Todas destas que foram utilizadas na implementa¸c˜ ao deste trabalho de conclus˜ ao s˜ ao apresentadas a seguir, nas se¸c˜ oes 4.2.1, Cria¸ c˜ ao e jun¸ c˜ ao;

e 4.2.2, Sincroniza¸ c˜ ao. A se¸c˜ ao 4.2.3 d´ a uma breve vis˜ ao sobre o conceito de Thread Specific Data (TSD), uma forma de recuperar a semˆ antica de vari´ aveis globais em um programa feito com Pthreads.

4.2.1 Cria¸ c˜ ao e jun¸ c˜ ao

Na biblioteca Pthread, uma thread ´ e criada usando a chamada pthread_create.

Essa fun¸c˜ ao recebe um ponteiro para uma outra fun¸c˜ ao que ir´ a definir o c´ odigo da thread, e um outro ponteiro para uma vari´ avel que ser´ a passada como parˆ ametro para a fun¸c˜ ao apontada. A assinatura completa segue abaixo:

int pthread_create(pthread_t * thread, pthread_attr_t * attr,

void * (*start_routine)(void *), void * arg);

A vari´ avel apontada de tipo pthread_t serve de identificador da thread. Como todos tipos definidos pelo padr˜ ao, ele ´ e um tipo opaco, ou seja, n˜ ao deve ser assumido nada sobre o seu formato, se ´ e um inteiro, ou uma estrutura, etc. O parˆ ametro attr pode alterar uma s´ erie de caracter´ısticas da thread, como por exemplo seu escopo:

de sistema ou de usu´ ario, em implementa¸c˜ oes que suportam os dois tipos. Para a

maioria dos casos, no entanto, o valor NULL pode ser passado como attr, criando

uma thread “padr˜ ao”.

(20)

Quando termina a thread principal do processo (aquela que executa a fun¸c˜ ao main), todo o processo ´ e terminado, sem esperar o t´ ermino de cada thread que foi disparada. Por isso, ´ e pr´ atica comum fazer a fun¸c˜ ao main aguard´ a-las, usando a fun¸c˜ ao pthread_join. Essa fun¸c˜ ao, com a assinatura logo abaixo, faz a jun¸c˜ ao da thread passada como argumento ` a thread chamadora, aguardando o seu final, liberando os recursos utilizados por ela, e opcionalmente guardando seu valor de retorno.

int pthread_join(pthread_t th, void **thread_return);

A listagem 6.5 mostra um c´ odigo gerado pelo M2PI usando as fun¸c˜ oes pthread_- create e pthread_-join. Estas s˜ ao as fun¸c˜ oes mais b´ asicas da Pthreads, e pro- gramas concorrentes podem ser escritos usando somente elas. Normalmente por´ em, em programas de alguma complexidade, dados compartilhados s˜ ao acessados pelas threads, ou elas precisam executar uma s´ erie distribu´ıda de passos em uma ordem coerente. Nestes casos, ´ e preciso sincronizar as threads.

4.2.2 Sincroniza¸ c˜ ao

Normalmente um programa de certa complexidade possui algumas estruturas de dados compostas que, quando alteradas, passam temporariamente por um estado inconsistente. Por exemplo, uma lista duplamente encadeada, ao ter um de seus elementos removidos, necessariamente passar´ a por um estado onde os elos de um dos lados deste elemento j´ a foram ajustados e do outro lado n˜ ao, fazendo com que a lista seja diferente dependendo do sentido em que ´ e percorrida.

Quando m´ ultiplos fluxos de execu¸c˜ ao est˜ ao acessando dados compartilhados, este tipo de inconsistˆ encia precisa ser escondido de todos os fluxos menos daquele que est´ a efetuando a altera¸c˜ ao. O c´ odigo que executa a altera¸c˜ ao constitui uma se¸ c˜ ao cr´ıtica (SC), um trecho de c´ odigo que deve ser executado por no m´ aximo uma thread ao mesmo tempo. Ent˜ ao as threads precisam coordenar, ou em outras palavras, sincronizar, sua execu¸c˜ ao de uma SC.

S˜ ao apresentados agora trˆ es mecanismos diferentes de sincroniza¸c˜ ao, o mutex, as vari´ aveis de condi¸ c˜ ao, e as barreiras

2

.

4.2.2.1 Mutex

Um dos principais recursos da Pthreads ´ e um mecanismo de exclus˜ ao m´ utua, o mutex. Um mutex est´ a trancado ou destrancado

3

. Quando uma thread deseja en- trar em uma se¸c˜ ao cr´ıtica, ela tranca o mutex com a fun¸c˜ ao pthread_mutex_lock, fun¸c˜ ao esta que s´ o retorna quando o mutex tiver sido “capturado” para esta thread.

Se o mutex estiver alocado para outra thread, a requisitante ficar´ a presa aguardando que a atual dona do mutex o destranque com a fun¸c˜ ao pthread_mutex_unlock.

Antes de ser usado, o mutex – identificado por um tipo abstrato pthread_mutex_t – deve ser inicializado pela fun¸c˜ ao pthread_mutex_init, e quando n˜ ao for mais

2

Cabe lembrar aqui que sem´ aforos s˜ ao uma outra alternativa muito popular de sincroniza-

¸

c˜ ao em programas multithreaded, mas s˜ ao defindos por uma norma diferente da fam´ılia POSIX.

BUTENHOF argumenta que o uso do mutex e de vari´ aveis de condi¸ c˜ ao ´ e prefer´ıvel na maioria dos casos, por sua maior simplicidade.

3

Por vezes ser˜ ao usados neste relat´ orio os termos “alocado” e “desalocado”, principalmente

quando se tratar de um recurso que ´ e protegido por um mutex, e quando o contexto n˜ ao per-

mitir confus˜ ao com o sentido de “aloca¸ c˜ ao de mem´ oria”.

(21)

necess´ ario, deve ser destru´ıdo pela pthread_mutex_destroy, liberando recursos do sistema. Alternativamente o mutex pode ser inicializado com uma constante PTHREAD_MUTEX_INITIALIZER. Seguem as assinaturas destas fun¸c˜ oes:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

4.2.2.2 Vari´ aveis de condi¸ c˜ ao

Al´ em do mutex para exclus˜ ao m´ utua, o POSIX threads disponibiliza um recurso de comunica¸c˜ ao, atrav´ es das vari´ aveis de condi¸c˜ ao (VC). Uma VC est´ a relacionada a um predicado, como “a lista est´ a vazia”, ou “a lista est´ a cheia”. Uma thread pode escolher aguardar at´ e que o predicado se torne verdadeiro, para isso chamando a fun¸c˜ ao pthread_cond_wait. Quando uma thread altera dados de forma a satisfazer a condi¸c˜ ao, ela sinaliza esse fato, “acordando” uma (com a pthread_cond_signal), ou

todas (com a pthread_cond_broadcast) threads que est˜ ao esperando no pthread_cond_wait por aquela VC. As assinaturas destas fun¸c˜ oes e das de inicializa¸c˜ ao e destrui¸c˜ ao de

vari´ aveis de condi¸c˜ ao s˜ ao listadas abaixo:

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

int pthread_cond_destroy(pthread_cond_t *cond);

Como pode-se notar pelas assinaturas, uma vari´ avel de condi¸c˜ ao est´ a sempre associada a um mutex, que garantir´ a o acesso exclusivo ao objeto da condi¸c˜ ao pelas threads envolvidas. As fun¸c˜ oes de sinaliza¸c˜ ao n˜ ao alteram o mutex, sendo respon- sabilidade do programador cuidar de destranc´ a-lo antes ou depois da chamada de sinaliza¸c˜ ao. Mas as fun¸c˜ oes de espera, como a pthread_cond_wait, sempre de- vem ser chamadas com o mutex relacionado trancado pela thread chamadora. A fun¸c˜ ao destrancar´ a o mutex, e quando ela retornar, ap´ os a condi¸c˜ ao ser satisfeita e sinalizada, ele estar´ a novamente trancado.

E importante n˜ ´ ao assumir que o predicado seja verdadeiro ap´ os o retorno do wait, testando a condi¸c˜ ao antes prosseguir, pois esta pode j´ a n˜ ao ser mais ver- dadeira quando a thread conseguir trancar o mutex (BUTENHOF (1997), na p´ agina 80, explica trˆ es situa¸c˜ oes onde isso ´ e poss´ıvel: predicados aproximados, wakeups in- terceptados, e wakeups esp´ urios). A fun¸c˜ ao de recebimento de mensagens do M2PI por exemplo, toma essa precau¸c˜ ao. No cen´ ario descrito na figura 6.1 da se¸c˜ ao 6.4.2, ela insere um descritor de recebimento de dados no canal de comunica¸c˜ ao do M2PI, e ent˜ ao aguarda a sinaliza¸c˜ ao de que o descritor j´ a foi processado pela thread que faz o envio do dados. Isso ´ e feito com o seguinte c´ odigo:

hqPush(M2PI_CHANNELS[src][dst].recv, handler);

while (handler->done == 0)

(22)

pthread_cond_wait(&handler->cond_done,

&M2PI_CHANNELS[src][dst].mutex);

A vari´ avel done do descritor handler ´ e o predicado da condi¸c˜ ao, e ´ e assinalada como verdadeira pela fun¸c˜ ao que faz o envio dos dados, antes desta sinalizar a VC.

A fun¸c˜ ao de recebimento aguarda a condi¸c˜ ao dentro de um la¸co que testa o valor de done, acordando realmente apenas quando o predicado for verdadeiro.

4.2.2.3 Barreiras

Uma outra primitiva de sincroniza¸c˜ ao muito ´ util, fornecida por uma extens˜ ao do padr˜ ao Pthreads original, ´ e a barreira. Uma barreira provˆ e um “ponto de encontro”

para um grupo de threads. A barreira deve ser inicializada uma vez com um conta- dor de valor N > 0, pela fun¸c˜ ao pthread_barrier_init. Cada thread interessada na barreira chama a fun¸c˜ ao pthread_barrier_wait, que decrementa o contador e depois espera at´ e que o contador chegue a zero, para s´ o retornar. Ou seja, todas threads que chegam a instru¸c˜ ao de wait, s˜ ao liberadas ao mesmo tempo, s´ o ap´ os a chegada da en´ esima thread. As fun¸c˜ oes tem as seguintes assinaturas:

int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count);

int pthread_barrier_wait(pthread_barrier_t *barrier);

int pthread_barrier_destroy(pthread_barrier_t *barrier);

Barreiras s˜ ao utilizadas no M2PI para fazer uma implementa¸c˜ ao muito simples da fun¸c˜ ao de broadcast do MPI, apresentada na se¸c˜ ao 6.5.

4.2.3 Thread-Specific Data

Thread-Specific Data (TSD) ´ e um recurso das POSIX Threads que permite a declara¸c˜ ao de vari´ aveis que tenham um mesmo nome em todas as threads, mas que tenham valores diferentes para cada uma delas. Seu objetivo ´ e facilitar a utiliza¸c˜ ao de dados que, em um programa que n˜ ao usa threads, seriam normalmente alocados estaticamente na sua ´ area de mem´ oria, como vari´ aveis globais ou vari´ aveis locais declaradas com o qualificador static. E ´ e exatamente para traduzir esse tipo de vari´ avel que o TSD ´ e interessante para a implementa¸c˜ ao do M2PI.

A desvantagem deste recurso, no entanto, ´ e que a sintaxe de leitura e escrita dessas vari´ aveis TSD ´ e completamente diferente da usual, exigindo chamadas de fun¸c˜ oes para realizar essas opera¸c˜ oes, al´ em de chamadas de fun¸c˜ ao para cria¸c˜ ao e destrui¸c˜ ao de uma “chave” de acesso ` a vari´ avel. Como o M2PI ´ e um tradutor autom´ atico, esta inconveniˆ encia n˜ ao seria grande empecilho para sua utiliza¸c˜ ao.

N˜ ao obstante, existe outro recurso equivalente, com sintaxe mais transparente, chamado Thread Local Storage (TLS), disponibilizado pelo compilador C uti- lizado pelo M2PI. Por sua simplicidade, foi decidido usar o TLS em favor do TSD Posix neste trabalho. A se¸c˜ ao 4.3 a seguir apresenta maiores detalhes sobre seu funcionamento.

4.3 Thread Local Storage

Thread Local Storage (TLS) ´ e um mecanismo suportado pelo GNU Compiler

Collection (GCC), que permite a defini¸c˜ ao de vari´ aveis globais e est´ aticas de forma

(23)

que exista uma instˆ ancia de cada vari´ avel para cada thread, e todo o trabalho de cri- a¸c˜ ao, acesso, e destrui¸c˜ ao dessas vari´ aveis ´ e feito pelo compilador e pelas bibliotecas do sistema operacional.

O TLS ´ e uma alternativa ao Thread Specific Data, que simplifica a utiliza¸c˜ ao da funcionalidade que os dois disponibilizam. Tudo que ´ e preciso fazer ´ e declarar a vari´ avel em quest˜ ao com o modificador __thread. A partir de ent˜ ao, o uso dela ´ e absolutamente idˆ entico ao de uma vari´ avel comum, como pode ser visto na listagem 6.11 mostrada na se¸c˜ ao 6.3. ´ E interessante notar que o operador C que retorna o endere¸co de uma vari´ avel – o & – quando aplicado a uma vari´ avel TLS, tem seu resultado computado em tempo de execu¸c˜ ao, e tal endere¸co pode ser utilizado por qualquer outra thread. Mas quando uma thread se encerra, todos ponteiros para vari´ aveis TLS desta thread se tornam inv´ alidos.

O manual do GCC d´ a mais detalhes sobre a aplicabilidade e as limita¸c˜ oes do Thread Local Storage

4

.

4

http://gcc.gnu.org/onlinedocs/gcc-4.3.0/gcc/Thread 002dLocal.html#Thread 002dLocal

(24)

5 ANTLR: COMPILADOR DE COMPILADORES

O ANTLR (ANother Tool for Language Recognition) ´ e um compilador de com- piladores, que gera c´ odigo para analisadores sint´ aticos e l´ exicos a partir da defini¸c˜ ao de uma gram´ atica aumentada. A gram´ atica ´ e dita “aumentada”, pois a¸c˜ oes s˜ ao as- sociadas com as regras de gera¸c˜ ao da gram´ atica, sendo executadas quando o parser gerado encontra constru¸c˜ oes correspondentes no texto de entrada. As a¸c˜ oes s˜ ao es- pecificadas como trechos de c´ odigo feitos na linguagem alvo, a linguagem na qual o c´ odigo dos analisadores ´ e gerado. E s˜ ao as a¸c˜ oes que transformam o texto anal- isado, seja em um outro programa escrito na mesma linguagem de entrada, seja em um programa de uma linguagem completamente diferente. Assim, a gram´ atica n˜ ao apenas valida a pertinˆ encia da entrada a uma linguagem, mas tamb´ em compila uma nova sa´ıda a partir dela.

Os parsers gerados pelo ANTLR usam um algoritmo top-down LL(*), uma ex- tens˜ ao do algoritmo LL(k) que permite um n´ıvel de lookahead arbitr´ ario. Uma descri¸c˜ ao do algoritmo LL(*) e dos recursos do ANTLR ´ e dada de forma detalhada em PARR (2007). A referˆ encia canˆ onica sobre a constru¸c˜ ao de compiladores ´ e AHO;

SETHI; ULLMAN (1986).

Este cap´ıtulo visa apresentar alguns dos recursos do ANTLR importantes para a implementa¸c˜ ao deste trabalho. A se¸c˜ ao 5.1 apresenta a sintaxe da defini¸c˜ ao das regras da gram´ atica; a se¸c˜ ao 5.2 discute as maneiras pelas quais as regras podem alterar o texto analisado e gerar um novo texto estruturado; e por fim a utiliza¸c˜ ao dos escopos do ANTLR ´ e descrita na se¸c˜ ao 5.3.

5.1 Regras

As regras no ANTLR se assemelham a regras de uma defini¸c˜ ao formal de gram´ ati- cas. Eis um exemplo de regra, que define instru¸c˜ oes de desvio incondicional na gram´ atica da linguagem C:

jump_statement

: ’goto’ IDENTIFIER ’;’

| ’continue’ ’;’

| ’break’ ’;’

| ’return’ ’;’

| ’return’ expression ’;’

;

Esta regra define as constru¸c˜ oes derivadas a partir de jump_satement, que por

este fato – derivar outras constru¸c˜ oes – ´ e chamado de um n˜ ao-terminal. As alter-

(25)

nativas de constru¸c˜ oes s˜ ao separadas por uma barra vertical (|), e podem referenciar outros n˜ ao-terminais, como ´ e o caso da ´ ultima alternativa do exemplo, que refer- encia expression. N˜ ao-terminais no ANTLR s˜ ao sempre iniciados por uma letra min´ uscula.

As palavras goto, continue, break, return, ;, e IDENTIFIER s˜ ao terminais (ou tokens ), que est˜ ao associados diretamente a palavras ou s´ımbolos presentes no texto sendo analisado. Terminais podem ser especificados como palavras literais, en- tre aspas, ou por express˜ oes regulares (ERs), identificadas por nomes que come¸cam por uma letra mai´ uscula. O terminal IDENTIFIER ´ e definido pela express˜ ao abaixo (LETTER ´ e um fragmento de uma ER, usado apenas para a reutiliza¸c˜ ao de subex- press˜ oes, n˜ ao podendo ser utilizado em regras de deriva¸c˜ ao de n˜ ao-terminais).

IDENTIFIER

: LETTER (LETTER|’0’..’9’)*

fragment LETTER

: ’$’

| ’A’..’Z’

| ’a’..’z’

| ’_’

;

As a¸ c˜ oes podem ser especificadas nas regras colocando o c´ odigo associado entre chaves. Por exemplo, se fosse desejado listar todas as instru¸c˜ oes goto de um ar- quivo fonte, a regra jump_statement exibida anteriormente poderia ser alterada da seguinte forma:

jump_statement

: ’goto’ id=IDENTIFIER ’;’ {System.out.println(’GOTO: ’ + $id.text);}

| ’continue’ ’;’

...

Como se vˆ e neste ´ ultimo exemplo, um terminal como o IDENTIFIER possui um valor (atributo .text), contendo o texto que foi casado pela ER. Tamb´ em ´ e poss´ıvel atribuir um outro nome pelo qual o token pode ser referenciado, como ´ e feito com o nome id acima.

5.2 Reescrita do fluxo de tokens e Modelos

O ANTLR disponibiliza duas maneiras de gerar o texto resultante de uma com- pila¸c˜ ao, al´ em da alternativa trivial de usar as a¸c˜ oes para imprimir cada linha da sa´ıda a partir de uma fun¸c˜ ao como a System.out.println do Java. S˜ ao a op¸c˜ ao de reescrita do fluxo de tokens gerados pelo analisador l´ exico, e o uso de modelos (templates ). A primeira ´ e recomendada para altera¸c˜ oes mais simples, e a segunda pode ser usada para casos complexos.

A reescrita ´ e ativada colocando-se a op¸c˜ ao rewrite = true no cabe¸calho da

gram´ atica utilizada. Quando isso ´ e feito, o objeto input – que cont´ em o fluxo

de tokens fornecidos pelo analisador l´ exico – passa a ser uma instˆ ancia da classe

(26)

TokenRewriteStream, que fornece m´ etodos para alter´ a-lo, permitindo a inser¸c˜ ao, dele¸c˜ ao e substitui¸c˜ ao de tokens. Essas opera¸c˜ oes de altera¸c˜ ao s˜ ao primeiramente armazenadas, e realizadas somente quando ´ e emitido o texto de sa´ıda ao final da compila¸c˜ ao, mantendo o buffer de tokens inalterado durante a an´ alise. O uso desse recurso ´ e muito simples, como ´ e demonstrado nas listagens 6.7 e 6.9 na se¸c˜ ao 6.3.

Os modelos funcionam como um tipo especializado de a¸c˜ ao, permitindo a ger- a¸c˜ ao de texto estruturado diretamente a partir dos terminais e n˜ ao-terminais que comp˜ oem as alternativas de uma regra, al´ em de poder utilizar resultados produzi- dos por outras a¸c˜ oes executadas junto com ele. Cada modelo possui um texto que o define, e esse texto pode conter lacunas que s˜ ao preenchidas quando o modelo ´ e

“chamado” (similarmente a uma chamada de fun¸c˜ ao). As defini¸c˜ oes dos modelos s˜ ao especificadas em um arquivo separado, informado ao compilador em tempo de exe- cu¸c˜ ao. Portanto ´ e poss´ıvel trocar o conjunto de modelos, alterando a sa´ıda gerada, sem modificar o compilador.

O uso de modelos na implementa¸c˜ ao do M2PI ´ e apresentado nas se¸c˜ oes 6.1 e 6.2.

Para utilizar um exemplo mais simples neste momento, sup˜ oe-se uma gram´ atica cujo objetivo seja reformatar defini¸c˜ oes de fun¸c˜ ao segundo uma nova norma de estilo de programa¸c˜ ao (a fun¸c˜ ao deste exemplo ´ e apenas mostrar os elementos sint´ aticos do uso de modelos em um caso simples). Nesse caso, uma regra como a seguinte poderia ser utilizada:

function_definition

: t=type id=IDENTIFIER ’(’ al=arg_list ’)’ b=block -> modelo_func(type={$t.text},

name={$id.text}, args={$al.text}, block={$b.text})

;

O operador -> indica a aplica¸c˜ ao de um modelo, para o qual podem ser passa- dos trechos de texto como parˆ ametros. No exemplo acima s˜ ao passados todos os elementos reconhecidos pela regra. Um exemplo de defini¸c˜ ao do modelo que poderia ser utilizada neste caso segue abaixo:

modelo_func(type,name,args,block) ::= <<

<type>

<name>(<args>)

/**** come¸ co da fun¸ c~ ao <name> ****/

<block>

/**** final da fun¸ c~ ao <name> ****/

>>

A defini¸c˜ ao do modelo ´ e delimitada por << e >>, e os parˆ ametros podem ser referenciados pelo seu nome entre < e >. Qualquer uso destes caracteres no texto literal do modelo deve ser escapado com uma barra invertida.

E poss´ıvel escolher entre aplicar ou n˜ ´ ao um modelo (ou aplicar modelos difer-

entes), com base em predicados semˆ anticos. Estes predicados ativam ou desati-

vam alternativas de um regra, e podem tamb´ em ser usados em outros casos em que

n˜ ao se apliquem modelos. ´ E um recurso ´ util para especificar a¸c˜ oes que devem ser

(27)

tomadas em casos especiais, com base n˜ ao na estrutura do texto analisado, mas no seu valor. Se fosse agora desejado alterar o estilo apenas das fun¸c˜ oes definidas para o M2PI por exemplo, poderia ser utilizada a regra a seguir.

function_definition

: t=type id=IDENTIFIER {$id.text.beginsWith("M2PI_")}?

’(’ al=arg_list ’)’ b=block -> modelo_func(type={$t.text}, name={$id.text},

args={$al.text}, block={$b.text})

| type IDENTIFIER ’(’ arg_list ’)’ block

;

A primeira alternativa ´ e selecionada apenas quando o nome da fun¸c˜ ao come¸ca com “M2PI ”, aplicando ent˜ ao o modelo. Caso contr´ ario, o texto de entrada per- manece inalterado. E importante atentar que a alternativa mais espec´ıfica deve ´ vir antes da mais geral, pois elas s˜ ao testadas em ordem, e a primeira que tiver o predicado verdadeiro ser´ a selecionada.

5.3 Escopos

Mapeando diretamente um conceito chave em linguagens de programa¸c˜ ao, o ANTLR implementa escopos como uma de suas palavras-chave (scope). Um scope define uma s´ erie de atributos e pode ser associado a uma ou mais regras. Cada vez que uma destas regras for “executada”, uma nova instˆ ancia desse escopo ser´ a empilhada no in´ıcio, e desempilhada ao final, automaticamente.

A listagem 6.8, na se¸c˜ ao 6.3, mostra a utiliza¸c˜ ao deste recurso na implementa¸c˜ ao

do M2PI.

(28)

6 IMPLEMENTA¸ C ˜ AO

Este cap´ıtulo apresenta a implementa¸c˜ ao do M2PI de forma bastante detalhada.

Para um bom entedimento das se¸c˜ oes por vir, ´ e preciso alguma familiaridade com os conceitos e ferramentas apresentados nos cap´ıtulos sobre MPI (4.1), Threads (4.2), e sobre o ANTLR (5).

Como j´ a foi mencionado, a implementa¸c˜ ao do M2PI se divide em duas partes principais: o compilador, e a biblioteca de tempo de execu¸c˜ ao. O compilador ´ e feito em Java usando o ANTLR, e aproveitando uma gram´ atica C que ´ e distribu´ıda junto com ele, como exemplo. Esta gram´ atica ´ e uma tradu¸c˜ ao da tradicional gram´ atica ANSI C para o Yacc distribu´ıda por Jutta Degener na Internet

1

, e apesar de n˜ ao suportar a sintaxe completa usada por compiladores C reais, como o da Intel ou do projeto GNU (que possuem muitas extens˜ oes ao padr˜ ao ANSI), ´ e suficiente para os fins deste trabalho. As trˆ es primeiras se¸c˜ oes abaixo tratam das fun¸c˜ oes realizadas pelo compilador do M2PI.

Tanto o c´ odigo gerado pelo compilador M2PI, quanto a biblioteca de tempo de execu¸c˜ ao (runtime) s˜ ao feitos na linguagem C, usando a biblioteca de threads POSIX. A biblioteca runtime implementa fun¸c˜ oes an´ alogas as do padr˜ ao MPI, se restringindo ao subconjunto definido na se¸c˜ ao 3.2. E com a exce¸c˜ ao das fun¸c˜ oes de inicializa¸c˜ ao e finaliza¸c˜ ao, todas as fun¸c˜ oes MPI deste subconjunto s˜ ao imple- mentadas integralmente pelo c´ odigo da biblioteca, sem aux´ılio de c´ odigo gerado diretamente pelo compilador, que nesses casos apenas faz uma substitui¸c˜ ao no pre- fixo do nome das fun¸c˜ oes, de “MPI” para “M2PI”

2

. ` A exce¸c˜ ao das se¸c˜ oes 6.1 e 6.3, todas a se¸c˜ oes a seguir tratam da implementa¸c˜ ao de fun¸c˜ oes na biblioteca runtime.

O processo de desenvolvimento deste projeto foi em geral orientado pelo teste da compila¸c˜ ao e execu¸c˜ ao de programas MPI que exigem, cada um deles, um sub- conjunto maior do padr˜ ao, motivando o acr´ escimo de novos recursos ao M2PI. Este cap´ıtulo se organiza da mesma forma, seguindo a ordem em que o desenvolvimento foi realizado, e frequentemente apresentando trechos dos programas de teste para auxiliar a explica¸c˜ ao dos recursos implementados.

6.1 Reconhecimento das chamadas de fun¸ c˜ oes MPI

Como a¸c˜ oes especiais precisam ser tomadas pelo compilador M2PI ao encontrar chamadas ` as fun¸c˜ oes do MPI, as regras da gram´ atica C utilizada foram alteradas de

1

Dispon´ıvel em http://www.lysator.liu.se/c/ANSI-C-grammar-y.html.

2

Isso pode parecer estranho, dado que a concep¸ c˜ ao deste trabalho enfatiza o uso de t´ ecnicas de

compiladores para realizar a tradu¸ c˜ ao. Uma reflex˜ ao sobre este aspecto da implementa¸ c˜ ao ´ e feita

na se¸ c˜ ao 6.6 e tamb´ em nas considera¸c˜ oes finais.

(29)

forma a especializar o reconhecimento destas fun¸c˜ oes, tratando-as como se fossem palavras chave da linguagem.

Isso foi feito encontrando a regra da gram´ atica que reconhece chamadas de fun¸c˜ ao, e colocando uma alternativa adicional, um n˜ ao-terminal que deriva nas chamadas espec´ıficas do MPI, como ´ e mostrado na listagem 6.1. A regra postfix_- expression deriva, entre outras coisas, chamadas de fun¸c˜ ao, e possui uma alter- nativa que deriva no n˜ ao-terminal mpi_call, cujas alternativas derivam por sua vez algumas das chamadas MPI. Essas alternativas repassam os argumentos das fun¸c˜ oes MPI aos modelos do ANTLR que geram o c´ odigo de sa´ıda. Esses argu- mentos s˜ ao reconhecidos de forma posicional, cada um deles ´ e um n˜ ao-terminal assignment_expression (a lista de parˆ ametros em uma chamada de fun¸c˜ ao ´ e definida como uma s´ erie de assignment_expressions, separados por v´ırgula). Na listagem abaixo, as longas listas de parˆ ametros das fun¸c˜ oes de comunica¸c˜ ao s˜ ao abreviadas como “.. args ..” para facilitar a leitura.

Listagem 6.1: Reconhecimento das chamadas de fun¸c˜ ao MPI

postfix expression : mpi call

| primary expression ( ... | ’ ( ’ ’ ) ’

| ’ ( ’ argument expression list ’ ) ’

| ’++’

| ... )∗

; mpi call

: ’MPI Comm size’ ’(’ a=assignment expression ’,’ b=assignment expression ’)’

−> m2pi comm size(comm={$a.text},address={$b.text})

| ’MPI Comm rank’ ’(’ a=assignment expression ’,’ b=assignment expression ’)’

−> m2pi comm rank(comm={$a.text},address={$b.text})

| ’MPI Send’ ’(’ .. args .. ’ ) ’ −> m2pi send(.. args ..)

| ’MPI Recv’ ’(’ .. args .. ’ ) ’ −> m2pi receive(.. args ..)

| ’MPI Bcast’ ’(’ .. args .. ’ ) ’ −> m2pi bcast(.. args ..)

;

Os modelos nestes casos fazem muito pouco, apenas traduzem o nome da chamada de fun¸c˜ ao e, nas trˆ es fun¸c˜ oes de comunica¸c˜ ao, agrupam os argumentos de tipo e n´ umero de elementos a serem transmitidos em um parˆ ametro s´ o, a quantidade de mem´ oria a ser transmitida. O modelo m2pi_bcast, por exemplo, ´ e definido assim:

m2pi_bcast(buf, count, type, root, comm) ::=

"M2PI_Bcast(<buf>, ((<count>) * sizeof(<type>)), <root>, <comm>)"

Como mostra a listagem 6.2, as fun¸c˜ oes MPI_Init e MPI_Finalize s˜ ao um caso especial. Elas n˜ ao s˜ ao associadas a modelos, pois o c´ odigo de inicializa¸c˜ ao e finaliza-

¸c˜ ao do M2PI ´ e gerado em outro ponto da gram´ atica. Al´ em disso, elas s˜ ao tratadas

como instru¸c˜ oes (statements ), e n˜ ao como express˜ oes, pois ´ e assumido neste trabalho

que elas ser˜ ao usadas assim, sem retorno de valor. Essa fun¸c˜ oes delimitam o que

o M2PI considera como c´ odigo que ser´ a executado por cada thread, e por isso s˜ ao

feitas algumas anota¸c˜ oes sobre as posi¸c˜ oes dos tokens encontrados durante o recon-

hecimento delas. Essas posi¸c˜ oes ser˜ ao utilizadas posteriormente, como ´ e explicado

na se¸c˜ ao 6.2.1.

(30)

Listagem 6.2: Reconhecimento das chamadas MPI_Init e MPI_Recv

statement

: compound statement

| iteration statement

| jump statement

| ...

| mpi statement

;

mpi statement

: b=’MPI Init’ ’(’ argument expression list ’ ) ’ e=’; ’ { mpi init begin = b.getTokenIndex();

mpi init end = e.getTokenIndex();

}

| b=’MPI Finalize’ ’(’ ’ ) ’ ’ ; ’ { mpi finalize begin = b.getTokenIndex();}

;

6.2 Primeiro programa: Hello World em MPI

A listagem 6.3 mostra um programa Hello World em MPI. A transforma¸c˜ ao deste c´ odigo foi o primeiro alvo a ser alcan¸cado na implementa¸c˜ ao deste trabalho, pois ´ e o c´ odigo MPI mais simples poss´ıvel, mas mesmo assim j´ a exige as fun¸c˜ oes mais b´ asicas do compilador M2PI e de sua biblioteca de execu¸c˜ ao, a saber: a cria¸c˜ ao das threads e extra¸c˜ ao do c´ odigo que ser´ a executado por elas; o comunicador global (MPI_COMM_WORLD); e a atribui¸c˜ ao de n´ umeros (ranks) ` as threads.

Listagem 6.3: Hello World usando MPI

1 int main(int argc, char∗ argv []) { 2 int p, r ;

3 MPI Init(&argc, &argv);

4 MPI Comm size(MPI COMM WORLD, &p);

5 MPI Comm rank(MPI COMM WORLD, &r);

6 printf (”Oi pessoal , do processo %d dentro de %d!\n”, r, p) ; 7 MPI Finalize();

8 return 0 ; 9 }

As subse¸c˜ oes abaixo apresentam a implementa¸c˜ ao destas fun¸c˜ oes b´ asicas. O apˆ endice A exibe o c´ odigo transformado completo do Hello-World MPI.

6.2.1 Threads em lugar de processos

Para disparar threads ao inv´ es de processos, o c´ odigo C original deve ser anal- isado, de forma a identificar e separar o trecho de c´ odigo que cada thread deve executar (linhas 4-6 no programa da listagem 6.3), e inserir no seu lugar o c´ odigo que dispara as N threads (uma informa¸c˜ ao conhecida s´ o em tempo de execu¸c˜ ao), entre outras tarefas de inicializa¸c˜ ao e finaliza¸c˜ ao.

Este trabalho assume que todo o c´ odigo da fun¸c˜ ao main original reside entre as chamadas MPI_Init e MPI_Finalize, a exce¸c˜ ao das declara¸c˜ oes de vari´ aveis.

Assim, o c´ odigo que vem antes do MPI_Init ´ e aproveitado pelo M2PI, pois nor-

malmente conter´ a declara¸c˜ oes de vari´ aveis, mas qualquer c´ odigo que venha depois

da MPI_Finalize ´ e completamente descartado. Esta decis˜ ao ´ e uma simplifica¸c˜ ao

Imagem

Referências

temas relacionados :