FICHA CATALOGRÁFICA ELABORADA PELA BIBLIOTECA DO IMECC DA UNICAMP
Bibliotecária: Maria Fabiana Bezerra Müller – CRB8 / 6162
Vieira, Cristianno Martins
V673p Paralelização automática de laços para arquiteturas multicore/Cristianno Martins Vieira-- Campinas, [S.P. : s.n.], 2010.
Orientador : Sandro Rigo.
Dissertação (mestrado) - Universidade Estadual de Campinas, Instituto de Computação.
1.Processadores multicore. 2.Arquitetura de computador. 3.Politopos. I. Rigo, Sandro. II. Universidade Estadual de Campinas. Instituto de Computação. III. Título.
Título em inglês: Automatic loop parallelization for multicore architectures
Palavras-chave em inglês (Keywords): 1. Multicore processors. 2. Computer architecture. 3. Polytopes.
Titulação: Mestre em Ciência da Computação
Banca examinadora: Prof. Dr. Sandro Rigo (IC - UNICAMP)
Prof. Dr. Guido Costa Souza de Araújo (IC – UNICAMP)
Prof. Dr. Ricardo Ribeiro dos Santos (Faculdade de Computação - UFMS)
Data da defesa: 08/11/2010
Programa de Pós-Graduação: Mestrado em Ciência da Computação
Instituto de Computa¸c˜ao Universidade Estadual de Campinas
Paraleliza¸
c˜
ao Autom´
atica de La¸
cos para
Arquiteturas Multicore
Cristianno Martins Vieira
1Novembro de 2010
Banca Examinadora:
• Prof. Dr. Sandro Rigo (Orientador)
• Prof. Dr. Guido Ara´ujo
Instituto de Computa¸c˜ao (Unicamp)
• Prof. Dr. Ricardo Ribeiro dos Santos
Faculdade de Computa¸c˜ao (UFMS)
• Prof. Dr. Ricardo Pannain (Suplente)
Instituto de Computa¸c˜ao (Unicamp)
• Prof. Dr. Roberto Hexel (Suplente)
Departamento de Inform´atica (UFPR)
1Suporte financeiro de: Bolsa Fapesp (processo 2008/52922-0) 2008–2010.
Resumo
Embora muitos programas possuam uma forma regular de paralelismo, que pode ser
expressa em termos de la¸cos paralelos, muitos exemplos importantes n˜ao a possuem. Loop
skewing ´e uma transforma¸c˜ao que remodela o espa¸co de itera¸c˜ao dos la¸cos para que seja
poss´ıvel expressar o paralelismo impl´ıcito atrav´es de la¸cos paralelos. Como consequˆencia
da complexidade em se modificar o espa¸co de itera¸c˜ao dos la¸cos, e de poss´ıveis problemas
causados por transforma¸c˜oes deste tipo – como o poss´ıvel aumento na taxa de miss em
caches –, no geral, elas n˜ao s˜ao largamente utilizadas.
Neste projeto, implementamos a transforma¸c˜ao loop skewing sobre o compilador da
lin-guagem C presente no GCC (GNU Compiler Collection), de forma a permitir a assistˆencia
pelo programador. Utilizamos a ferramenta Graphite como base para a implementa¸c˜ao
da otimiza¸c˜ao, apenas representando-a como uma transforma¸c˜ao afim sobre um objeto
matem´atico multidimensional chamado pol´ıtopo.
Mostramos, atrav´es de um estudo detalhado sobre o modelo matem´atico denominado
modelo polit´opico, que la¸cos com estruturas espec´ıficas – perfeitamente aninhados, com
limites e acesso `a mem´oria descritos por fun¸c˜oes afins – poderiam ser representados como
pol´ıtopos, e que transforma¸c˜oes aplicadas a estes seriam espelhadas no c´odigo gerado a
partir desses pol´ıtopos. Dessa forma, qualquer transforma¸c˜ao que possa ser estruturada
como uma transforma¸c˜ao afim sobre um pol´ıtopo, poder´a ser implementada.
Mostramos, ainda, durante a an´alise de desempenho, que transforma¸c˜oes deste tipo s˜ao
vi´aveis e, apesar de algumas limita¸c˜oes impostas pela infraestrutura do GCC, aumentam
relativamente o desempenho das aplica¸c˜oes compiladas com ela – obtivemos um ganho
m´aximo de aproximadamente 115% para o uso de quatro threads em uma das aplica¸c˜oes
executadas. Verificamos o impacto do uso de programas j´a paralelizados manualmente
sobre a plataforma, e obtivemos um ganho m´aximo de 11% nesses casos, mostrando que
ainda aplica¸c˜oes paralelizadas podem conter paralelismo impl´ıcito.
Abstract
Although many programs present a regular form of parallelism, which can be expressed as parallel loops, many important examples do not. Loop skewing is a transformation that reorganizes the iteration space of loops to make it possible to expose the implicit parallelism through parallel loops. In general, as a consequence of the complexity in modifying the iteration space of loops, and possible problems caused by such changes – such as the possibility of increasing the miss rate in caches –, they are not widely used.
In this work, the loop skewing transformation was implemented on GCC’s C compiler (GNU Compiler Collection), allowing programmer’s assistance. Graphite provides us a basis for implementation of the optimization, just representing it as an affine transforma-tion on a multidimensional mathematical object called polytope.
We show, through a detailed study about the mathematical model called polytope mo-del, that for a very restricted loop structure – perfectly nested, with limits and memory accesses described by affine functions - could be represented as polytopes, and transforma-tions applied to these would be carried by the code generated from these polytope. Thus, any transformation that could be structured as an affine transformation on a polytope, could be added.
We also show, by means of performance analysis, that this type of transformation is feasible and, despite some limitations imposed by the still under development GCC’s in-frastructure for auto-parallelization, fairly increases the performance of some applications compiled with it – we achived a maximum of about 115% using four threads with one of the applications. We also verified the impact of using manually parallelized programs on this platform, and achieved a maximum gain of 11% in these cases, showing that even parallel applications may have implicit parallelism.
Agradecimentos
Este trabalho se tornou poss´ıvel gra¸cas a muitas pessoas que me ajudaram de diferentes formas. Gostaria de citar todas elas explicitamente, mas sei que dificilmente esta lista estaria incompleta.
Antes de mais nada, eu gostaria de agradecer ao meu orientador Sandro Rigo, que
sempre esteve dispon´ıvel para mim quando eu precisei – n˜ao importava se a raz˜ao fosse
alguma d´uvida t´ecnica, um problema administrativo, ou “apenas” planejando o futuro.
Obrigado, Sandro, por seu apoio em qualquer situa¸c˜ao.
Tamb´em gostaria de agradecer aos membros do Laborat´orio de Sistemas de
Com-puta¸c˜ao da Unicamp: apesar de n˜ao estar presente durante grande parte do
desenvol-vimento deste trabalho, sempre obtive apoio dos colegas dal´ı. Gostaria de agradecer principalmente ao amigo Rodrigo Faveri pelas caronas, conversas, viagens (onde tive a
oportunidade de conhecer n˜ao s´o mais um peda¸co do Brasil, mas sua admir´avel fam´ılia),
enfim, pela paciˆencia para com um colega t˜ao distante de seu lar.
Entretanto, h´a tamb´em muitas outras pessoas que me ajudaram fora dos colegas da
´
area. Sou muito grato a Robson Peixoto, colega de mestrado, com quem eu tive algumas
discuss˜oes frut´ıferas em v´arios t´opicos sobre este projeto. Obrigado, Robson, por sua
colabora¸c˜ao produtiva.
Agrade¸co imensamente aos amigos Marcos Vieira e Rodrigo Tripodi pelas horas dedi-cadas `as revis˜oes e aux´ılio a tradu¸c˜ao de textos, sem os quais eu realmente n˜ao conseguiria
as duas ou trˆes horas de sono por dia (merecidas) em per´ıodos pr´oximos a deadlines.
Agrade¸co tamb´em aos amigos cuiabanos Daniel Vecchiato e Willian Maja, e ao mineiro
Douglas Leite, principalmente pelos grupos de estudo, que nos fortalecia o conhecimento
pelo compartilhamento do mesmo. Obrigado tamb´em aos demais originais companheiros
do ”Bubbles”, que (bom) cresceu tanto durante esses dois anos: Thiago Lechuga e Maria
A. Souza; n˜ao esqueci de vocˆes – s´o pensei em guardar o melhor para o final ;). Espero
sinceramente que nossas vidas, ainda que separadas, sincronizem-se em algum momento e voltemos a nos encontrar =D
Em resumo, gostaria de agradecer a todos os meus amigos, novos e antigos. Vocˆes me
forneceram o equil´ıbrio perfeito entre motiva¸c˜ao e energia para trabalhar, ao mesmo tempo
que a distˆancia necess´aria disto e uma importante conex˜ao com o mundo al´em do trabalho.
Agrade¸co, particularmente, `a Isaura Souto que, por vezes, tirou-me do confinamento quase
que `a for¸ca, ainda que fosse para “dar uma volta”; suas visitas s˜ao muito agrad´aveis. `A
Amanda Nascimento pelos cavaqueios, pelos conselhos, pela sinceridade, enfim, por ser
uma boa amiga. Tamb´em a Victor Seino e Caroline Barros, colegas de pens˜ao e, acima
disso, amigos, que da mesma forma se dispuseram, principalmente no per´ıodo mais cr´ıtico
do projeto: durante a escrita da disserta¸c˜ao. Obrigado tamb´em aos demais colegas da
pens˜ao; ainda que n˜ao citados explicitamente, tenham a certeza que sou muito grato por
seu companheirismo, amizade e carinho.
Agrade¸co, ainda, ao suporte direto `as minhas atividades de pesquisa dado pela Funda¸c˜ao
de Amparo `a Pesquisa do Estado de S˜ao Paulo, que me permitiu permanecer em dedica¸c˜ao
exclusiva atrav´es de bolsa pelo per´ıodo relativo a este projeto.
Da universidade, gostaria de agradecer primeiramente ao secretariado do Instituto de
Computa¸c˜ao (IC), formado por Fernando Okabe, Fl´avio Luzia, Wilson Bagni, Ademilson
Ramos e Daniel Capeleto: muito obrigado pelo suporte, eficiˆencia e muitas informa¸c˜oes
prestados n˜ao s´o a mim, mas a todos os alunos de gradua¸c˜ao e p´os do IC. Acredito que falo
em nome de todos quando digo que nem imagino nossas vidas acadˆemicas sem seu apoio.
Muit´ıssimo obrigado `as pessoas que tive contato tamb´em junto ao SAPPE/UNICAMP,
em especial Dra. C´elia, que se tornou um conforto e ombro amigo no per´ıodo que precisei.
Gostaria de agradecer tamb´em a todos os demais funcion´arios da Unicamp (m´edicos,
faxi-neiros, seguran¸cas, jardifaxi-neiros, etc.) que se esfor¸cam para manter um ambiente agrad´avel
para n´os, alunos.
Por ´ultimo, mas n˜ao menos importante, obrigado a todos os membros da minha fam´ılia,
principalmente aos meus pais que, de certo, sofreram, assim como eu, por meu tempo
limitado. Obrigado especialmente `a minha m˜ae, que me garantiu a possibilidade de seguir
este caminho. Muito obrigado por seu amor, que tem sido uma fonte infinita de energia para mim.
Agrade¸co a Deus por todos vocˆes.
“Todas as coisas grandes deste mundo s˜ao feitas por pessoas ingˆenuas
que tˆem uma ideia obviamente imposs´ıvel.”
Sum´
ario
Resumo v
Abstract vi
Agradecimentos vii
1 Introdu¸c˜ao 1
2 Revis˜ao Bibliogr´afica 4
3 Conceitos B´asicos 11
3.1 Conceitos Alg´ebricos . . . 12
3.1.1 Modelo Polit´opico . . . 14
3.2 Alcan¸cando o Paralelismo . . . 19
3.2.1 An´alise das Dependˆencias . . . 20
3.2.2 Transforma¸c˜oes . . . 24
4 Materiais e M´etodos 29 4.1 GNU Compiler Collection . . . 29
4.2 Graphite . . . 31
4.2.1 Polyhedral Compilation Package . . . 33
4.2.2 GPOLY: GIMPLE representado atrav´es de pol´ıtopos . . . 37
4.3 Autopar . . . 41
4.4 Implementa¸c˜ao . . . 45
5 Experimentos 50
6 Conclus˜oes e Trabalhos Futuros 57
A Tabelas de Resultados 60
Lista de Tabelas
A.1 Tabela que mostra as medidas de speedup e eficiˆencia para os programas
selecionados no Phoronix Text Suite para a execu¸c˜ao com 4 threads. . . 63
A.2 Tabela que mostra as medidas de speedup e eficiˆencia para os programas
selecionados no Phoronix Text Suite para a execu¸c˜ao com 8 threads. . . 66
A.3 Tabela que mostra as medidas de speedup e eficiˆencia para os programas
selecionados no Phoronix Text Suite para a execu¸c˜ao com 12 threads. . . . 69
Lista de Figuras
2.1 Classifica¸c˜ao hier´arquica das abordagens para paraleliza¸c˜ao de aplica¸c˜oes . 5
3.1 Representa¸c˜ao gr´afica de espa¸co bidimensional . . . 15
3.2 Exemplo de Loop Statement Tree para o algoritmo 3.3 . . . 17
3.3 Padr˜ao de dependˆencias trabalhadas por loop skewing . . . 26
4.1 Representa¸c˜oes intermedi´arias do GCC . . . 30
4.2 Representa¸c˜oes intermedi´arias do GCC e sua fus˜ao com o Graphite . . . . 31
4.3 Vis˜ao geral dos m´odulos que comp˜oem o Graphite . . . 32
4.4 Representa¸c˜oes em GIMPLE, Pcp e GPOLY . . . 35
4.5 Arquitetura do PCP . . . 36
4.6 Exemplo de detec¸c˜ao de SCoP . . . 38
4.7 Dependˆencias entre os acessos a vetores no algoritmo 4.3(c) . . . 43
5.1 Speedup para as aplica¸c˜oes selecionadas do Phoronix Test Suite . . . 51
5.2 Eficiˆencia para as aplica¸c˜oes selecionadas do Phoronix Test Suite . . . 53
Lista de Algoritmos
3.1 Estruturas perfeitamente aninhadas de la¸cos . . . 12
3.2 Exemplo de passeio sobre pol´ıtopo da figura 3.1 . . . 15
3.3 N´ucleo da fatora¸c˜ao de Cholesky . . . 17
3.4 Trecho de c´odigo que ilustra dependˆencia de dados . . . 20
3.5 Trecho de c´odigo que ilustra dependˆencia c´ıclica de dados . . . 20
3.6 Exemplo de dependˆencia entre diferentes itera¸c˜oes . . . 22
3.7 Exemplo de dependˆencia entre diferentes itera¸c˜oes . . . 24
3.8 Algoritmo que ilustra dependˆencias de dados em todas as dimens˜oes do la¸co 25 3.9 Algoritmo com diagonal projetada para a dimens˜ao J . . . 26
3.10 Algoritmo ap´os aplica¸c˜ao de loop skewing . . . 27
4.1 Exemplo de c´odigo para constru¸c˜ao de pol´ıtopo . . . 40
4.2 Exemplo de estrutura aninhada de la¸cos com dependˆencia n˜ao muito simples 42 4.3 Exemplos de la¸cos ignorados pelo Autopar . . . 44
4.4 La¸co paralelo reescrito da figura 4.3(c) . . . 44
4.5 Estruturas de la¸co perfeitamente aninhadas geradas a partir de la¸co ani-nhado imperfeito . . . 46
4.6 Exemplo de la¸co perfeitamente aninhado com padr˜ao de dependˆencias com-plexo . . . 46
4.7 La¸co exibido na figura 4.6 ap´os transforma¸c˜oes . . . 47
4.8 Pseudoc´odigo da transforma¸c˜ao loop skewing . . . 49
Cap´ıtulo 1
Introdu¸
c˜
ao
Uma aplica¸c˜ao em software ´e a ponte entre o usu´ario e a plataforma de hardware. Com a
evolu¸c˜ao da tecnologia dos ´ultimos anos, o que vemos ´e uma quantidade cada vez maior
de dados que precisa ser processada em um per´ıodo cada vez menor de tempo. Isso mo-tiva os engenheiros de hardware a buscar avan¸cos em arquiteturas de microprocessadores
e tecnologia de fabrica¸c˜ao, visando criar plataformas capazes de suprir as necessidades
das novas aplica¸c˜oes. At´e recentemente, principalmente no que se refere a processadores
de prop´osito geral (usados em computadores pessoais, por exemplo), o aumento de
de-sempenho era alcan¸cado atrav´es de sucessivos aumentos na densidade de transistores e
frequˆencia do processador. Isto desencadeou s´erios problemas de projeto, principalmente
relacionados a consumo de energia e dissipa¸c˜ao de calor. Al´em disso, mesmo que o tempo
necess´ario para um impulso el´etrico trafegar num processador seja muito pequeno, ele n˜ao
´e igual a zero, o que imp˜oe uma barreira f´ısica como limite para o clock do processador.
Essas limita¸c˜oes obrigaram os projetistas de grandes fabricantes de processadores a buscar
novos modelos de arquitetura de computadores.
Um modelo de computa¸c˜ao j´a conhecido h´a bastante tempo e utilizado principalmente
para aplica¸c˜oes cient´ıficas ´e o processamento em paralelo. Para entender como esse modelo
funciona, basta comparar o processador a um c´erebro humano. ´E sabido que o tempo
gasto para um impulso nervoso transitar entre neurˆonios ´e muito maior que o de um
sinal el´etrico trafegando em um circuito. Por´em, pela existˆencia de milh˜oes de neurˆonios
trabalhando em conjunto, o modelo do c´erebro ´e mais eficiente, sendo, portanto, superior.
Assim, adaptando-se ao modelo do c´erebro, projetistas adicionaram v´arios “neurˆonios
eletrˆonicos” (n´ucleos de processamento) simples e confi´aveis a um mesmo processador.
Esse novo modelo mais especificamente visa combinar elementos m´ultiplos – tais como
sistemas de comunica¸c˜ao, barramentos, caches e unidades l´ogicas e aritm´eticas – em um
´
unico chip, objetivando aumentar o paralelismo em n´ıvel de tarefas [6, 48]. Dessa forma, os novos projetos de processadores e sistemas utilizam multiprocessamento para atingir
2
os requisitos de desempenho, sem comprometer o projeto com problemas relacionados ao
calor dissipado. Esses novos processadores s˜ao denominados processadores multicore e
tornaram-se os principais produtos de empresas como Intel (Core2Duo e Core2Quad) e
AMD (Opteron e Athlon X2) para computa¸c˜ao de prop´osito geral.
O principal problema ´e que aplica¸c˜oes sequenciais – tamb´em conhecidas como
single-threaded – n˜ao conseguem tirar proveito das caracter´ısticas das novas arquiteturas
multi-core ou multiprocessadas. Em outras palavras, o desempenho de uma aplica¸c˜ao desse tipo
n˜ao melhora simplesmente por executarmos o programa em um processador multicore. E
existe uma grande quantidade de aplica¸c˜oes codificadas de forma sequencial que poderiam
aproveitar-se das vantagens dos sistemas multicore. Muitas aplica¸c˜oes multim´ıdia – como
algoritmos de processamento de imagens, por exemplo – s˜ao naturalmente paraleliz´aveis.
Contudo, a paraleliza¸c˜ao de aplica¸c˜oes n˜ao ´e uma tarefa trivial. O principal problema
´e encontrar o paralelismo na aplica¸c˜ao e efetivamente dividir aquela tarefa de forma a
aproveitar ao m´aximo a m´aquina alvo.
Com o objetivo de atingir um ganho razo´avel de desempenho minimizando a carga
sobre o desenvolvedor, pesquisadores tˆem desenvolvido ferramentas que auxiliam a
para-leliza¸c˜ao [10]. Entretanto, mais interessante seria se linguagens como C/C++, muito
comuns no desenvolvimento de aplica¸c˜oes de prop´osito geral e para sistemas
dedica-dos, oferecessem na pr´opria linguagem mecanismos para ajudar no particionamento da
aplica¸c˜ao. Mais que isso, o compilador, que j´a possui uma sofisticada tecnologia de an´alise
e otimiza¸c˜ao de c´odigo, poderia agregar a paraleliza¸c˜ao como uma de suas otimiza¸c˜oes,
desobrigando ao desenvolvedor a interven¸c˜ao no processo de paraleliza¸c˜ao.
No entanto, o paralelismo introduz muito mais complexidade ao processo de
oti-miza¸c˜ao. Primeiro, porque ´e necess´ario escolher trechos de c´odigo que tragam ganhos reais
`
a aplica¸c˜ao. Na maioria dos casos, dada otimiza¸c˜ao restringe seu dom´ınio de aplica¸c˜ao.
La¸cos, ou fun¸c˜oes recursivas, que tendem a ser partes de c´odigo executadas in´umeras
vezes, s˜ao fortes candidatos `a paraleliza¸c˜ao. Ainda assim, por influˆencia de limitantes,
como dependˆencias nos acessos aos dados, nem todos estes trechos de c´odigo podem ser
aproveitados.
Al´em disso, encontrar paralelismo atrav´es da an´alise est´atica do c´odigo nem sempre
nos d´a uma vis˜ao concreta dos acessos aos dados realizados durante a execu¸c˜ao de uma
aplica¸c˜ao. O uso de apontadores ou acessos irregulares a vetores (como o uso de vetores
para indexar vetores) deturpam os resultados das an´alises de dependˆencias, e o c´odigo,
muitas vezes independente, n˜ao pode ser paralelizado; al´em disso, na maioria dos casos o
paralelismo n˜ao est´a explicitado na aplica¸c˜ao. Nestes casos, ´e necess´aria uma boa an´alise
de dependˆencias por parte do compilador, ou um bom processo de paraleliza¸c˜ao deveria
ser capaz de detectar o paralelismo impl´ıcito.
3
skewing, uma transforma¸c˜ao que pode reorganizar as itera¸c˜oes de la¸cos perfeitamente
ani-nhados de forma a mover as dependˆencias de dados para os la¸cos mais externos. As
prin-cipais vantagens desta abordagem s˜ao o tratamento do paralelismo, mesmo que impl´ıcito
na aplica¸c˜ao, e a garantia da independˆencia das itera¸c˜oes nos la¸cos internos, deixando-os
completamente paraleliz´aveis.
Neste projeto, uma abordagem baseada no modelo matem´atico denomidado modelo
polit´opico foi adotada para a implementa¸c˜ao de transforma¸c˜oes sobre o c´odigo
inter-medi´ario do GCC. Primeiramente, um estudo aprofundado sobre as aplica¸c˜oes do modelo
polit´opico foi realizado. Fundamentalmente, este modelo permite a representa¸c˜ao de um
conjunto de restri¸c˜oes por objetos matem´aticos multidimensionais chamados poliedros.
Certas tranforma¸c˜oes (como rota¸c˜ao e transla¸c˜ao) podem ser aplicadas a esses objetos,
desde que elas possam ser representadas por um conjunto de opera¸c˜oes descritas por
fun¸c˜oes afins. Um estudo sobre representa¸c˜ao de hierarquia e dependˆencias presentes em
estruturas aninhadas de la¸cos foi realizado, a fim de representar trechos de c´odigo em
al-guma linguagem de programa¸c˜ao atrav´es de pol´ıtopos, garantindo que exista a tradu¸c˜ao
inversa (de pol´ıtopo para a linguagem de programa¸c˜ao). Ao representar transforma¸c˜oes
complexas (como loop skewing, por exemplo) por um conjunto de transforma¸c˜oes sobre
pol´ıtopos, esse tipo de transforma¸c˜ao pode ser facilmente aplicado aos la¸cos represent´aveis
pelo modelo. Essa disserta¸c˜ao, ent˜ao, foca na aplica¸c˜ao de transforma¸c˜oes que exp˜oem
paralelismo sobre la¸cos aninhados utilizando o modelo polit´opico como base.
O restante do texto est´a organizado como se segue: o pr´oximo cap´ıtulo descreve
tra-balhos relacionados ao tema deste projeto. O cap´ıtulo seguinte fornece alguns conceitos
b´asicos; no cap´ıtulo 4, s˜ao apresentados os materiais e a solu¸c˜ao implementada sobre o
GCC (GNU Compiler Collection) para a paraleliza¸c˜ao de aplica¸c˜oes desenvolvida neste
trabalho; os resultados das execu¸c˜oes de programas reais compilados com a nova solu¸c˜ao
e sua an´alise s˜ao apresentados no cap´ıtulo 5 para a valida¸c˜ao da solu¸c˜ao; o cap´ıtulo
se-guinte realiza as considera¸c˜oes finais e exp˜oe trabalhos futuros sobre o tema; e, por fim,
Cap´ıtulo 2
Revis˜
ao Bibliogr´
afica
Devido a limitantes f´ısicos para o tamanho e temperatura dos circuitos, avan¸cos recentes
em microarquitetura tˆem na replica¸c˜ao de n´ucleos de processamento uma esperan¸ca para
ganho de desempenho. Atualmente, mesmo sistemas embarcados j´a possuem
processado-res formados por mais de um n´ucleo [32].
Al´em desses processadores aumentarem o paralelismo permitindo que uma maior
quan-tidade de dados seja processada por vez, a proximidade dos n´ucleos de processamento
tamb´em minimiza o tempo de comunica¸c˜ao entre eles. Este fator ´e uma importante
caracter´ıstica das novas arquiteturas: enquanto v´arias aplica¸c˜oes distintas podem ser
exe-cutadas nos v´arios n´ucleos presentes no processador, caso uma aplica¸c˜ao paralela seja
executada, ela tamb´em deve tirar proveito dos v´arios n´ucleos. Para tal, quando alguma
sincroniza¸c˜ao se fizer necess´aria, haver´a a comunica¸c˜ao entre os n´ucleos, que n˜ao ocorre
numa vers˜ao serial da mesma aplica¸c˜ao. A comunica¸c˜ao, neste caso, representa apenas
overhead e, portanto, ´e desej´avel gastar o m´ınimo de tempo para que ela ocorra.
Mas, ao contr´ario do que acontecia com o aumento do desempenho do sistema ao
aumentar a frequˆencia do processador, por exemplo, a mudan¸ca de um processador de
n´ucleo simples para um com v´arios n´ucleos n˜ao ´e o bastante para diminuir o seu tempo de
resposta. Se, por um lado, essas novas arquiteturas aumentam a taxa de throughput1 [40],
permitindo o processamento de v´arias tarefas simultaneamente, para aplica¸c˜oes que n˜ao
fazem uso intensivo de threads, ou sistemas que n˜ao s˜ao multitarefa, n˜ao haver´a qualquer
diferen¸ca no tempo de execu¸c˜ao.
Para que haja um aproveitamento real das aplica¸c˜oes de prop´osito geral, h´a duas
abordagens poss´ıveis: o programador reformula o c´odigo do programa para que ele usufrua
os m´ultiplos n´ucleos de processamento, seja atrav´es de paralelismo de threads, processos,
tarefas, etc., usando bibliotecas que exigem sincroniza¸c˜ao e divis˜ao de tarefas expl´ıcita; ou
o pr´oprio hardware, ou uma interface com o mesmo (como o compilador, por exemplo),
1Taxa que relaciona o n´umero de tarefas que entram com as tarefas que saem do sistema.
5
automaticamente reconheceria partes potencialmente paralelas do c´odigo, selecionando-as
para execu¸c˜ao concorrente. Uma classifica¸c˜ao hier´arquica para as abordagens discutidas
aqui pode ser vista na figura 2.1. A abordagem adotada neste projeto seria denominada
solu¸c˜ao autom´atica por software assistida pelo programador seguindo esta classifica¸c˜ao.
Paralelização
Manual + custosa
Automática
Solução por HW Combinada a SW (API)
Reprojeto de HW ñ flexível ñ escalável Solução por SW Semiautomatizada induzida Modelagem/Programação retreino de programadores Modificações em Compiladores Stand-alone auto-suficiente Assistida autoparallel.mmap - 5/9/2010 - Mindjet
Figura 2.1: Classifica¸c˜ao hier´arquica das abordagens para paraleliza¸c˜ao de aplica¸c˜oes. Na
figura, os acrˆonimos HW e SW fazem referˆencia a hardware e software, respectivamente.
´
E importante frisar que a distin¸c˜ao entre paraleliza¸c˜ao semiautomatizada e assistida se
d´a pelo grau de interferˆencia do programador: na primeira delas, o programador indica
os trechos de c´odigo a serem paralelizados, enquanto na segunda, o programador recebe
informa¸c˜oes sobre trechos onde a autoparaleliza¸c˜ao n˜ao pˆode ser aplicada com sucesso, e
este poder´a modificar o c´odigo para permitir sua aplica¸c˜ao.
A primeira alternativa claramente ´e a mais custosa, uma vez que anos de software
legado deveriam ser reprojetados para fazer uso dos recursos de hardware atuais. Neste
caso, a paraleliza¸c˜ao autom´atica n˜ao requereria tanto esfor¸co para realizar a mesma
ta-refa. Nesta vertente, alguns estudos sugerem mudan¸cas no hardware, provendo suporte
a opera¸c˜oes que permitam extrair o m´aximo do potencial dos processadores. Em [8], por
exemplo, um modelo de arquitetura multicore heterogˆenea em conjunto com uma API2
2Do inglˆes, Application Programming Interface ´e uma interface que um programa implementa para
6
espec´ıfica s˜ao discutidos como uma poss´ıvel solu¸c˜ao para problemas de sincroniza¸c˜ao
en-tre as threads de um programa. O processador seria formado por um n´ucleo principal,
onde a por¸c˜ao serial do programa executaria, e v´arios n´ucleos menores especializados em
processamento num´erico e sem mecanismos de preemp¸c˜ao, que seriam alocados a tarefas
paralelas.
Tamb´em, em [46] ´e apresentada a t´ecnica conhecida como Thread-Level Data
Specula-tion3 (TLDS), que representa o uso de especula¸c˜ao sobre dados na mem´oria para permitir
a sobreposi¸c˜ao da execu¸c˜ao de trechos interdependentes de c´odigo. Sua unidade m´ınima
de execu¸c˜ao ´e a thread, e, caso algum acesso `a mem´oria viole alguma das regras b´asicas
do modelo, seus c´alculos s˜ao descartados e a execu¸c˜ao daquela thread ´e reiniciada. Neste
artigo, pequenas modifica¸c˜oes no hardware (como uma extens˜ao do protocolo de coerˆencia
de cache para permitir a detec¸c˜ao de viola¸c˜ao de dependˆencias entre os processadores)
s˜ao sugeridas para tornar a t´ecnica mais agressiva. Ainda, em [47] o mesmo autor discute
o desempenho da implementa¸c˜ao de TLDS e sugere uma implementa¸c˜ao de algoritmo em
hardware para sincroniza¸c˜ao dinˆamica entre as threads.
Por outro lado, [51] sugere uma reformula¸c˜ao da arquitetura para processadores
mul-ticore, apresentando o processador Voltron como proposta, que adiciona `a arquitetura
convencional duas caracter´ısticas principais: uma rede de intercomunica¸c˜ao entre os
ban-cos de registradores dos n´ucleos, que aparentemente habilitaria sincroniza¸c˜ao entre os
n´ucleos atrav´es de uma comunica¸c˜ao com baixa latˆencia; e suporte a dois modos de
execu¸c˜ao, nos quais os n´ucleos poderiam ter o comportamento de um multicluster VLIW
(modo acoplado), explorando basicamente ILP (Instruction-Level Parallelism), ou
explo-rar paralelismo de gr˜aos mais grossos (modo desacoplado), do tipo TLP (Thread-Level
Parallelism) ou LLP (Loop-Level Parallelism).
Nenhuma dessas mudan¸cas, entretanto, ´e trivial, exigindo um reprojeto dos
proces-sadores para cada nova t´ecnica. Desse modo, al´em de n˜ao escal´avel, a solu¸c˜ao dada por
hardware ´e inflex´ıvel[17]. A paraleliza¸c˜ao por software, ent˜ao, claramente se torna mais
vi´avel.
Entretanto, apesar de haver uma longa hist´oria envolvendo paraleliza¸c˜ao de aplica¸c˜oes
cient´ıficas, existem alguns problemas ainda intrat´aveis – decorrentes de dependˆencias
com-plexas, por exemplo – al´em das diferen¸cas aparentes na estrutura de aplica¸c˜oes cient´ıficas
e aquelas de prop´osito geral, o que excluiria um grande n´umero de t´ecnicas geralmente
aplicadas `as primeiras, mas n˜ao t˜ao pr´oximas em contexto das ´ultimas. Geralmente,
ent˜ao, opta-se por aplica¸c˜ao de uma paraleliza¸c˜ao semiautomatizada (ou induzida), na
qual indica-se a um preprocessador onde o paralelismo pode ser explorado, e como se
3TLDS trata-se de uma varia¸c˜ao de Thread-Level Speculation (TLS), onde os valores dos dados
pre-sentes na mem´oria podem ser previstos. Em TLS, ´e permitida a execu¸c˜ao paralela de threads cujos c´odigos s˜ao potencialmente interdependentes, mas os resultados de sua execu¸c˜ao ´e apenas considerado caso nenhuma dependˆencia tenha sido violada.
7
comportam as dependˆencias naquele c´odigo, e este geraria automaticamente o c´odigo
pa-ralelo para a aplica¸c˜ao; outra alternativa ´e abordar a paraleliza¸c˜ao de casos espec´ıficos,
onde pode-se determinar a existˆencia de dependˆencias (como impor a restri¸c˜ao de
para-lelizar c´odigos com vetores, mas n˜ao com listas encadeadas, por exemplo).
Para auxiliar na paraleliza¸c˜ao semiautom´atica, existem algumas API que
implemen-tam o paralelismo e podem ser invocadas para iniciar e finalizar uma regi˜ao paralela
de c´odigo (onde as unidades processantes deveriam ser replicadas). Em [6], por
exem-plo, ´e apresentado o Multicore Framework, uma API para programa¸c˜ao de processadores
multicore heterogˆeneos. Esta API provˆe uma vis˜ao abstrata do hardware com rela¸c˜ao `a
computa¸c˜ao de conjuntos de dados multidimensionais. Em [2] ´e apresentada uma
biblio-teca de primitivas paralelas de c´odigo aberto chamada SWARM (Software and Algorithms
for Running on Multicore). Um conjunto de algoritmos, incluindo ordena¸c˜ao, sele¸c˜ao e
m´axima parsimˆonia, foi implementado utilizando esta biblioteca para avaliar sua facilidade
de uso e desempenho (apesar de sua implementa¸c˜ao ainda n˜ao encontrar-se totalmente
otimizada). ´
E discutido em [25] o Multicore Communication API (MCAPI), a especifica¸c˜ao de um
framework para constru¸c˜ao de algoritmos paralelos para processadores multicore baseada
em passagem de mensagens. O objetivo ao desenvolver essa especifica¸c˜ao era torn´
a-la um padr˜ao para arquiteturas multicore heterogˆeneas, uma vez que pThreads[43] foi
projetado para ambientes multiprocessados homogˆeneos – e a maioria das implementa¸c˜oes
OpenMP[11] s˜ao baseadas em pThreads. De maneira semelhante, o uso de frameworks
que implementam o padr˜ao MPI (Message Passing Interface)[45] n˜ao foram projetados
para essas arquiteturas, e poder˜ao n˜ao usufruir completamente de seus recursos.
Em alguns casos, mudan¸cas no pr´oprio modelo de programa¸c˜ao foram sugeridas, como
em [4], que oferece um sistema baseado em C para programa¸c˜ao paralela multithreaded.
Este sistema permite a cria¸c˜ao de threads a partir de um conjunto de comandos, e escalona
tarefas entre elas baseado em work-stealing.
Existem, ainda, ferramentas que auxiliam na escrita do c´odigo para ambas as
aborda-gens de paralelismo (autom´atica e manual): em [27] ´e apresentado o iPat/OMP, uma
ferra-menta para assistˆencia interativa `a paraleliza¸c˜ao que escolhe a correta diretiva OpenMP
para la¸cos a partir da assistˆencia do programador, enquanto em [22] ´e realizada uma
an´alise entre diferentes blocos b´asicos, e c´odigo alvo apropriado para a abordagem de
passagem de mensagens ´e gerado. O principal objetivo de tais ferramentas ´e tornar o
processo de paraleliza¸c˜ao o mais pr´oximo poss´ıvel da automatiza¸c˜ao. Em geral, ambas
as abordagens s˜ao melhor aplicadas a casos com caracter´ısticas espec´ıficas. Algumas
su-gest˜oes a mudan¸cas nos padr˜oes dessas abordagens, tornando a automatiza¸c˜ao de seu
uso mais gen´erica, como em [21], que prop˜oe algumas t´ecnicas de otimiza¸c˜ao para
8
ser encontradas na literatura.
Em comum a todas as abordagens mencionadas, a paraleliza¸c˜ao autom´atica
concentra-se em minerar partes de c´odigo que apresentam potencial para o paralelismo. Fun¸c˜oes
recursivas, por exemplo, geralmente apresentam algoritmos com estrat´egia do tipo
“di-vidir e conquistar”. Caso n˜ao existam dependˆencias, c´odigos baseados nesse tipo de
estrat´egia podem ser facilmente paralelizados [23]. Na maioria dos casos, no entanto,
compiladores paralelizantes focam em la¸cos, por potencialmente executarem por longos
per´ıodos de tempo, e possu´ırem um padr˜ao de acesso aos dados entre suas itera¸c˜oes.
Al-gumas aplica¸c˜oes, tais como jogos, som, v´ıdeo e aplica¸c˜oes cient´ıficas usam intensamente
opera¸c˜oes sobre conjuntos de dados em la¸cos, por exemplo.
Devido `a incerteza quanto `a independˆencia dos dados na autoparaleliza¸c˜ao, ´e bastante
comum o uso de heur´ısticas, que n˜ao garantem a solu¸c˜ao ´otima, mas produzem
rapida-mente um bom c´odigo paralelo na maioria dos casos. TLS, j´a citada anteriormente, ´e um
bom exemplo deste tipo de heur´ıstica. Em especial, ´e poss´ıvel ver em [39] o uso de TLS
para permitir a la¸cos aninhados o usufruto do potencial de processadores multicore. A
implementa¸c˜ao apresentada neste artigo n˜ao exigia modifica¸c˜oes no hardware atual, mas
obteve cerca de 15% de ganho, na m´edia, no tempo de execu¸c˜ao dos programas testados.
Outro uso de especula¸c˜ao na tentativa de driblar o problema das dependˆencias ´e
discutido em [35], onde, para la¸cos com dependˆencias, ´e escolhido o n´umero de itera¸c˜oes
a serem executadas em paralelo, considerando a probabilidade de ocorrer a viola¸c˜ao de
alguma dependˆencia naquele conjunto de itera¸c˜oes. Neste caso, os resultados tamb´em
mostraram ganho de cerca de 12% em m´edia.
Outro uso de especula¸c˜ao para maximizar o uso de multicore pode ser visto em [52],
onde algumas transforma¸c˜oes para la¸cos s˜ao apresentadas para diminuir a influˆencia das
dependˆencias sobre o desempenho da aplica¸c˜ao de TLS. S˜ao aplicadas transforma¸c˜oes,
tais quais fiss˜ao especulativa de la¸cos e isolamento de dependˆencias pouco frequentes,
mas ´e importante notar que a aplica¸c˜ao de transforma¸c˜oes que isolam as dependˆencias
(e, portanto, exp˜oem paralelismo) ajudam a aumentar o desempenho de t´ecnicas para
autoparaleliza¸c˜ao.
Uma aplica¸c˜ao mais interessante de especula¸c˜ao ´e encontrada em [15], onde ´e discutida
uma solu¸c˜ao baseada em alguns princ´ıpios do modelo polit´opico para a paraleliza¸c˜ao de
estruturas espec´ıficas de la¸cos: la¸cos do tipo for aninhados em um la¸co do tipo while que
itera sobre uma condi¸c˜ao convergente (estrutura presente em grande parte de aplica¸c˜oes
num´ericas). Neste caso, o pr´oprio n´umero de itera¸c˜oes do la¸co externo deve ser
especu-lado. No entanto, al´em de bastante espec´ıfico, o artigo deixa claro que nenhum teste foi
executado para verificar a solu¸c˜ao – trata-se apenas de um conceito – e n˜ao foi encontrado
9
Outra heur´ıstica adotada atualmente tem sido a especula¸c˜ao de traces de execu¸c˜ao4.
Em trabalhos como [48], traces s˜ao somente utilizados para auxiliar na visualiza¸c˜ao da
intera¸c˜ao entre m´ultiplas threads, enquanto em [7] h´a a implementa¸c˜ao de um modelo de
execu¸c˜ao paralelo baseado em traces. Na discuss˜ao sobre os resultados, a aplica¸c˜ao da
t´ecnica apresentou resultados muito parecidos com a TLDS.
Todavia, em determinados contextos, ´e poss´ıvel paralelizar automaticamente o c´odigo
por aplica¸c˜ao de procedimentos algor´ıtmicos, ao inv´es de usar heur´ısticas; a t´ecnica
deno-minada modelo polit´opico, por exemplo, que pode ser aplicada para otimiza¸c˜oes de la¸cos
com acesso `a mem´oria, limitantes e incremento descritos por fun¸c˜oes afins. Em [24] e [34]
´e aplicada a transforma¸c˜ao chamada tiling sobre la¸cos perfeitamente aninhados5. Em [5],
ainda, temos a cria¸c˜ao de um preprocessador que recebe um c´odigo em C e devolve outro
c´odigo tamb´em em C com tiling aplicada a todos os la¸cos que sejam poss´ıveis.
Em especial, em [37] s˜ao relatados os resultados dos testes realizados sobre o
com-pilador autoparalelizador OSCAR, avaliando seu desempenho sobre dois processadores
de quatro n´ucleos (o FR1000, com n´ucleos VLIW6, desenvolvido pela Fujitsu Ltda.; e o
RP1, com n´ucleos SH4A, desenvolvido num cons´orcio entre Renesas Technology Corp.,
Hitachi Ltda. e Waseda University). Ao contr´ario do trabalho desenvolvido neste projeto,
o OSCAR aproveita-se de informa¸c˜oes espec´ıficas da arquitetura para otimizar o c´odigo,
o que garante melhores resultados, mas ao custo de exigir uma sele¸c˜ao – e, talvez,
reim-plementa¸c˜ao – de otimiza¸c˜oes ´unica para cada nova arquitetura cujo suporte venha a ser
adicionado.
Ainda outro fator est´a relacionado com a qualidade do c´odigo gerado pelo compilador:
sendo que a maioria destas transforma¸c˜oes ´e implementada como otimiza¸c˜oes sobre o
c´odigo fonte, ´e importante notar que, a depender da aplica¸c˜ao, a ordem de otimiza¸c˜oes
de la¸cos ´e ´otima [49]. Compiladores mais simples apenas imp˜oem determinada ordem
na aplica¸c˜ao das otimiza¸c˜oes, enquanto outros utilizam a t´ecnica conhecida como
gerar-e-testar, onde todos os arranjos sobre otimiza¸c˜oes s˜ao testados; o primeiro claramente ignora
um ganho em potencial no desempenho das aplica¸c˜oes compiladas, enquanto o segundo
perde muito tempo na gera¸c˜ao do c´odigo. Em [9], utilizam-se t´ecnicas de aprendizado de
m´aquina para constru¸c˜ao de um compilador que escolha a melhor ordem para a aplica¸c˜ao
de otimiza¸c˜oes.
Demais disto, v´arios estudos sobre como melhor aproveitar-se do potencial das
arqui-4Originalmente desenvolvido para aumentar os blocos cont´ıguos de instru¸c˜oes para m´aquinas VLIW
(Very Long Instruction Word ), um trace trata-se de um poss´ıvel caminho tomado ao longo de um con-junto de branches no c´odigo. No processo de otimiza¸c˜ao, as decis˜oes mais comumente tomadas sobre branches s˜ao assumidas sempre tomadas em uma c´opia do c´odigo, que possui uma maior oportunidade de otimiza¸c˜ao (uma vez que os branches n˜ao se fazem necess´arios aqui).
5Um conjunto de la¸cos ´e dito estar perfeitamente aninhado se, para cada la¸co, ou ele possui exatamente
um la¸co em seu corpo, ou ele ´e o la¸co mais interno e possui instru¸c˜oes diferentes de um la¸co no seu interior.
10
teturas multicore tˆem sido realizados. Uma vis˜ao geral de quanto se tem desenvolvido
nesta ´area pode ser obtida em [30]. Nesta mesma pesquisa, o autor chama a aten¸c˜ao
para o fato que as linguagens de programa¸c˜ao mais utilizadas em aplica¸c˜oes comerciais
(C, C++, Java, etc.) foram escritas com base em paradigmas seriais e a paraleliza¸c˜ao
automatizada a partir das mesmas n˜ao ´e uma tarefa trivial.
Em se tratando especificamente de transforma¸c˜oes sobre la¸cos aninhados, muitas
abor-dagens tˆem sido implementadas e introduzidas a compiladores. A grande maioria delas
simplesmente reestrutura o c´odigo-fonte, devolvendo um c´odigo na mesma linguagem com
a transforma¸c˜ao aplicada aos la¸cos. ParaScope[16] e Polaris[3], por exemplo, s˜ao
para-lelizadores fonte-a-fonte para Fortran baseados em dependˆencias. Entretanto, s˜ao
ferra-mentas fundamentalmente acadˆemicas e, portanto, sem muito contato com programas
reais.
O PIPS [26] ´e um dos mais completos compiladores com reestrutura¸c˜ao de la¸cos,
imple-mentando an´alise polit´opica e transforma¸c˜oes, al´em de an´alise interprocedural. Ele utiliza
uma ´arvore sint´atica estendida com anota¸c˜oes para representar pol´ıtopos, dificultando um
pouco a implementa¸c˜ao de transforma¸c˜oes. O compilador MARS[38] unifica as id´eias de
transforma¸c˜oes de la¸cos baseadas em dependˆencias com otimiza¸c˜oes de armazenamento
de dados. No entanto, sua representa¸c˜ao intermedi´aria n˜ao ´e capaz de capturar todas as
informa¸c˜oes de la¸cos; somente dom´ınios e fun¸c˜oes de acesso s˜ao represent´aveis, n˜ao sendo poss´ıvel o armazenamento do escalonamento. O Graphite[41], por sua vez, objetiva ser
uma ferramenta modular de reestrutura¸c˜ao de c´odigo em representa¸c˜ao intermedi´aria, de
uso geral. Por ter sido constru´ıda inicialmente sobre uma ferramenta de produ¸c˜ao (GCC),
´e poss´ıvel notar que pretende-se aplic´a-lo a programas reais, e n˜ao apenas mantˆe-lo como
ferramental acadˆemico.
Este projeto utilizou o Graphite como base para a implementa¸c˜ao de transforma¸c˜oes
afins sobre la¸cos a fim de tornar suas itera¸c˜oes independentes. Para la¸cos nos quais n˜ao
foi poss´ıvel aplicar a t´ecnica, mensagens a partir do otimizador podem ser habilitadas
para auxiliar o programador a descobrir quais la¸cos estavam sendo reestruturados. Al´em
disso, uma an´alise do uso da ferramenta em aplica¸c˜oes reais mostrou que o desempenho
Cap´ıtulo 3
Conceitos B´
asicos
Programas passam a maior parte de seu tempo de execu¸c˜ao efetuando a mesma opera¸c˜ao
sobre diferentes conjuntos de dados. Logo, as estruturas que realizam o controle desta repeti¸c˜ao s˜ao pass´ıveis de serem paralelizadas. La¸cos e fun¸c˜oes recursivas s˜ao as principais raz˜oes de repeti¸c˜ao de c´odigo; essas estruturas geram, durante suas execu¸c˜oes, m´ultiplas
instˆancias das instru¸c˜oes em seus corpos.
Este trabalho concentra-se nas estruturas de la¸cos, representando-as atrav´es de
obje-tos matem´aticos, conhecidos por pol´ıtopos1. Ap´os a aplica¸c˜ao de um conjunto de
trans-forma¸c˜oes sobre esses pol´ıtopos, outra estrutura de la¸cos ´e gerada, otimizada para algum
crit´erio (c´odigo mais compacto, menor consumo de mem´oria, ou exposi¸c˜ao de
parale-lismo, por exemplo). Em especial, esta t´ecnica ´e somente aplic´avel a estruturas de la¸cos
perfeitamente aninhadas.
Defini¸c˜ao 1 (Aninhamento perfeito). Um conjunto de la¸cos ´e dito estar
perfeita-mente aninhado se, para cada la¸co, ou ele possui exataperfeita-mente um la¸co em seu corpo, ou ele ´e o la¸co mais interno e possui instru¸c˜oes diferentes de la¸cos no seu interior.
No algoritmo 3.1(a) ´e poss´ıvel visualizar um exemplo de la¸co aninhado. Esta defini¸c˜ao,
no entanto, pode ser relaxada, como foi implementada neste projeto, para que estruturas
de la¸cos como mostrada na figura 3.1(b) tamb´em sejam consideradas perfeitamente
ani-nhadas. Para isso, basta considerar o la¸co mais interno na figura (cuja vari´avel de indu¸c˜ao
´e k) como um bloco de instru¸c˜oes pertencente ao corpo do la¸co um n´ıvel acima. Dessa
forma, o la¸co de ´ındice j passa a ser o la¸co mais interno, e o aninhamento ´e perfeito.
Neste cap´ıtulo, ser˜ao discutidos conceitos b´asicos de ´algebra e paralelismo para auxiliar
no entendimento do que foi desenvolvido neste trabalho.
1Ou politopo.
3.1. Conceitos Alg´ebricos 12
Algoritmo 3.1: Estruturas perfeitamente aninhadas de la¸cos. O exemplo em (a)
segue a defini¸c˜ao 1, onde instru¸c˜oes diferentes de la¸cos somente aparecem no interior
o la¸co mais interno. Em (b), apenas os dois primeiros la¸cos (cujas vari´aveis de
indu¸c˜ao s˜ao i e j) formam uma estrutura perfeitamente aninhada, e o terceiro la¸co
pode ser visto como uma senten¸ca pertencente ao corpo do segundo la¸co. (a) for i ← 1 to 10 do for j ← i to 10 do for k ← i to 10 do S: a ← a + b ∗ c T: b ← b ∗ b end end end (b) for i ← 1 to 10 do for j ← i to 10 do T: b ← b ∗ b for k ← i to 10 do S: a ← a + b ∗ c end end end
3.1
Conceitos Alg´
ebricos
Por restri¸c˜oes presentes na representa¸c˜ao do modelo matem´atico a ser utilizado neste
pro-jeto, ´e exigido que os la¸cos cumpram alguns requisitos [19, 31]; esses la¸cos s˜ao conhecidos
como La¸cos de Controle Afim (Affine Control Loops, ACL).
Defini¸c˜ao 2 (Fun¸c˜ao linear). Uma fun¸c˜ao d0-dimensional f com d argumentos v1, . . . , vd
´e linear se ela pode ser expressa da seguinte forma:
linear f (v) = Mfv, onde v = v1 .. . vd e Mf ∈ R
d0×d ´e uma matriz com d0 linhas e d colunas.
No contexto do conjunto de ´ındices, s´o se faz interessante tratar com matrizes inteiras,
ou seja, Mf ∈ Zd
0×d
.
Defini¸c˜ao 3 (Fun¸c˜ao afim). Uma fun¸c˜ao d0-dimensional f com d argumentos v1, . . . , vd
´e af im se ela pode ser expressa da seguinte forma:
af im f (v) = Mfv + f0, onde v = v1 .. . vd , Mf ∈ R
d0×d ´e uma matriz com d0 linhas e d colunas, e f
0 ∈ R. Ou
3.1. Conceitos Alg´ebricos 13
Da mesma forma, para o problema da representa¸c˜ao do espa¸co de ´ındices do la¸co,
Mf ∈ Zd
0×d
e f0 ∈ Z.
´
E importante notar que, em ambas as defini¸c˜oes, ´e somente permitido a Mf e f0
possu´ırem valores inteiros, n˜ao podendo ser vari´aveis, mesmo que estas se comportem
como constantes simb´olicas2.
Defini¸c˜ao 4 (La¸co com controle afim). Um la¸co ´e dito com controle afim (ACL) se,
e somente se, ele satisfaz as seguintes condi¸c˜oes:
• Ser uma estrutura perfeitamente aninhada de la¸cos;
• Possuir limites superiores e inferiores de cada la¸co como fun¸c˜oes afins envolvendo
´ındices dos la¸cos mais externos e constantes;
• Possuir corpo formado apenas por comandos que acessam escalares ou vetores, com dependˆencia e acesso afim, isto ´e, n˜ao s˜ao permitidas dependˆencias irregulares (como vetores indexando vetores, por exemplo).
Outras defini¸c˜oes importantes, antes de esclarecer o modelo matem´atico utilizado, s˜ao
mostradas a seguir.
Defini¸c˜ao 5 (Hiperplano). Um hiperplano pode ser um espa¸co vetorial, transforma¸c˜ao
afim ou subespa¸co afim de dimens˜ao dim(H) = d − 1 de um espa¸co d-dimensional. Logo,
um hiperplano pode ser representado por uma equa¸c˜ao afim. Um hiperplano divide o
espa¸co em dois semiespa¸cos.
Em especial, em um espa¸co tridimensional, um hiperplano ´e o pr´oprio plano; num
plano, um hiperplano ´e uma reta; numa reta, um ponto.
Defini¸c˜ao 6 (Semiespa¸cos). Um semiespa¸co ´e o conjunto de pontos de um espa¸co
d-dimensional que est˜ao de um lado do hiperplano. Logo, todo semiespa¸co pode ser
representado por uma inequa¸c˜ao afim. Caso algum semiespa¸co inclua o hiperplano, a este
´e dado o nome de semiespa¸co fechado (e pode ser representado por H≤ ou H≥); caso
contr´ario, denomina-se semiespa¸co aberto (indicado por H< ou H>).
Note que, na perspectiva do conjunto Z, para qualquer hiperplano H, Zn= H≤∪ H> = H≥∪ H<
2O termo constante simb´olica ´e utilizado aqui para descrever valores que podem variar, mas nunca no
espa¸co em quest˜ao; em uma analogia `a programa¸c˜ao de computadores, seriam vari´aveis que n˜ao mudam de valor no bloco de c´odigo sendo analisado.
3.1. Conceitos Alg´ebricos 14
.
Sistemas de equa¸c˜oes afins s˜ao frequentemente representadas seguindo a forma normal
M v = 0, onde M ´e uma matriz r × d, r ´e o n´umero de equa¸c˜oes do sistema e d ´e o tamanho
do vetor de vari´aveis e parˆametros v. Semelhantemente, em sistemas de inequa¸c˜oes afins,
utiliza-se a forma normal M v ≥ 0.
3.1.1
Modelo Polit´
opico
Defini¸c˜ao 7 (Pol´ıtopo). `A intersec¸c˜ao de um n´umero finito de semiespa¸cos fechados
d´a-se o nome de pol´ıtopo3. Todo pol´ıtopo pode ser representado por uma desigualdade
matricial como
P =x ∈ Rn
: Ax ≤ b, A ∈ Rm×n, b ∈ R .
Como consequˆencia imediata da defini¸c˜ao 7, pode-se representar um pol´ıtopo por um
sistema de inequa¸c˜oes afins e, a partir da´ı, construir uma matriz M sobre este sistema:
cada linha da matriz representar´a um semiespa¸co.
Em [13], Philippe Clauss prop˜oe um m´etodo para contagem de inteiros dentro de um
pol´ıtopo usando a teoria dos polinˆomios de Ehrhart. Esta teoria pode ser vista como
uma generaliza¸c˜ao dimensional da f´ormula de Pick no plano Euclidiano4. A discuss˜ao
deste m´etodo foge ao escopo deste trabalho, e recomenda-se a leitura de [13] para maiores
detalhes. Para fins desta disserta¸c˜ao, ´e interessante apenas saber que o n´umero de pontos
inteiros internos a um pol´ıtopo ´e cont´avel.
Modelo Matem´atico de um Programa Fonte
O primeiro passo para aplicar otimiza¸c˜oes utilizando o modelo polit´opico ´e transferir
informa¸c˜oes relevantes sobre o programa sendo compilado para a representa¸c˜ao de um
pol´ıtopo. Para isso, ´e importante saber o que ´e poss´ıvel representar atrav´es do modelo.
Como mencionado anteriormente, este trabalho foca em otimiza¸c˜oes sobre la¸cos. Mais
que isso, restringimos o conjunto dos la¸cos a serem otimizados aos la¸cos ACL – la¸cos com
controle e acessos `a mem´oria descritos por fun¸c˜oes afins. Para criar um pol´ıtopo que
represente um ACL5, s˜ao quatro as principais informa¸c˜oes que devem ser armazenadas,
que ser˜ao ilustradas pelo problema descrito a seguir.
3Em se tratando de espa¸cos tridimensionais, pol´ıtopos s˜ao conhecidos por poliedros. Similarmente,
para duas dimens˜oes temos os pol´ıgonos, e pol´ıcoros em quatro dimens˜oes.
4A f´ormula de Pick ´e um teorema publicado em 1899 por George Alexander Pick, e trata-se de um
m´etodo simples para calcular a ´area de pol´ıgonos.
5Dado que as fun¸c˜oes afins extra´ıdas de la¸cos para a representa¸c˜ao do pol´ıtopo s˜ao (para la¸cos com
3.1. Conceitos Alg´ebricos 15
Suponha um conjunto de restri¸c˜oes afins que descrevem uma parte de um espa¸co
d-dimensional. O conjunto dessas restri¸c˜oes ´e chamado dom´ınio. Considere, sem perda
de generalidade, o seguinte conjunto de restri¸c˜oes, onde i e j s˜ao as duas dimens˜oes do
espa¸co (d = 2), e m e n s˜ao os parˆametros (constantes simb´olicas):
2 ≤ i ≤ n 2 ≤ j ≤ m j ≤ n + 2 − i
(3.1)
Considere, ainda, que se conhece parcialmente os valores dos parˆametros, chamado de
contexto, expressados tamb´em como inequa¸c˜oes afins:
m ≥ 2
n ≥ 2 (3.2)
Uma representa¸c˜ao gr´afica dessa parte do espa¸co bidimensional pode ser vista na figura
3.1. j>=2 j<=m i<=n i>=2 j 1 2 1 2 n i m j<=n+2-i
Figura 3.1: Representa¸c˜ao gr´afica de espa¸co bidimensional. Os pontos na figura mostram
os valores inteiros contidos no espa¸co ilustrado.
Um algoritmo para percorrer os pontos inteiros mostrados na figura, dados o dom´ınio e o contexto, pode ser visto no algoritmo 3.2.
Algoritmo 3.2: Exemplo de passeio sobre pol´ıtopo da figura 3.1. for i ← 2 to n do
for j ← 2 to m do S(i, j)
end end
3.1. Conceitos Alg´ebricos 16
Dados o contexto e o dom´ınio como sistemas de inequa¸c˜oes afins, como visto neste
exemplo, podem-se represent´a-los atrav´es de pol´ıtopos.
Note que o corpo do la¸co no algoritmo 3.2 ´e mostrado como uma fun¸c˜ao dos ´ındices
dos la¸cos em torno do bloco. Na verdade, n˜ao ´e importante representar completamente
as senten¸cas presentes no corpo do la¸co atrav´es do modelo, mas apenas as dependˆencias
entre elas (a importˆancia das dependˆencias para a otimiza¸c˜ao ´e discutida na se¸c˜ao 3.2.1).
Para ser poss´ıvel computar as dependˆencias, ´e necess´ario possuir conhecimento sobre a
intera¸c˜ao entre as opera¸c˜oes no conjunto de ´ındices.
Em ACLs apenas s˜ao permitidos vetores com acesso descrito por fun¸c˜oes afins,
poden-do-se tamb´em representar esses acessos por pol´ıtopos; cada linha da matriz representaria
uma dimens˜ao do vetor, e cada coluna, as dimens˜oes e parˆametros do pol´ıtopo. Assim,
todo acesso a uma vari´avel pode ser modelada por uma matriz de acesso, juntamente com
um identificador para o tipo de acesso como leitura ou escrita.
Por ´ultimo, os dom´ınios somente definem um conjunto de pontos inteiros a percorrer,
mas n˜ao especificam uma ordem para fazˆe-lo. Para ser poss´ıvel expressar a ordem de
execu¸c˜ao entre as senten¸cas presentes nos la¸cos, s˜ao usadas as fun¸c˜oes de escalonamento.
Defini¸c˜ao 8 (Fun¸c˜ao de escalonamento). Um escalonamento θ de um programa
´e uma fun¸c˜ao que mapeia toda opera¸c˜ao para um vetor inteiro que representa o tempo
l´ogico. Para toda senten¸ca S, seu escalonamento θS pode ser representado por uma matriz
Θt×d, onde t ´e o n´umero de dimens˜oes de tempo, e d ´e a dimensionalidade do conjunto de
´ındices de S mais o n´umero de parˆametros simb´olicos.
Em outras palavras, um escalonamento θS pode ser utilizado para indicar uma data
l´ogica para a execu¸c˜ao de uma senten¸ca, a partir das dimens˜oes originais, dos parˆametros
e de escalares. Em [18] ´e discutido um m´etodo bastante simples para calcular as fun¸c˜oes
de escalonamento. A ideia ´e construir uma Loop Statement Tree6 (LST) para o programa,
e ler o escalonamento para cada senten¸ca. Observe o algoritmo 3.3.
A LST correspondente ´e dada na figura 3.2. Para sua constru¸c˜ao, um n´o raiz n˜ao
nomeado ´e criado, com arestas dirigindo-se aos n´os que representam todos os la¸cos mais
externos do algoritmo; no exemplo, apenas o la¸co de ´ındice i encontra-se no n´ıvel mais externo. O n´o que representa este la¸co, ent˜ao, ´e nomeado por sua vari´avel de indu¸c˜ao, e a
aresta que leva at´e ele ´e nomeada por um ´ındice inteiro que representa a ordem cronol´ogica
para sua execu¸c˜ao (a ordem em que o la¸co aparece no programa). Internos ao la¸co de
´ındice i, encontram-se um la¸co de ´ındice j, a senten¸ca S2 e outro la¸co de ´ındice tamb´em j,
nesta ordem. Para senten¸cas, um n´o com um r´otulo identificador ´e criado. Novamente, as
arestas recebem identificadores que representam sua ordem no c´odigo original. Os passos
6Uma Loop Statement Tree ´e uma representa¸c˜ao em ´arvore da estrutura hier´arquica de la¸cos, que
3.1. Conceitos Alg´ebricos 17
Algoritmo 3.3: N´ucleo do algoritmo de fatora¸c˜ao de Cholesky.
for i ← 1 to n do for j ← 1 to i − 1 do S1: A[i][j] -= A[i][j]; end S2: A[i][i] = sqrt(A[i][i]); for j ← i + 1 to n do for k ← 1 to i − 1 do S3: A[j][i] -= A[j][k]*A[i][k]; end S4: A[j][i] /= A[i][i]; end end
descritos s˜ao repetidos para cada n´o que represente um la¸co at´e que todas as folhas na
´
arvore representem senten¸cas.
i j j k S2 S1 S4 S3 0 2 1 0 0 1 0 0
Figura 3.2: Exemplo de Loop Statement Tree para o algoritmo 3.3.
A partir dessa ´arvore, obtemos as fun¸c˜oes de escalonamento
θS1(i, j) T = (0, i, 0, j, 0)T θS2(i) T = (0, i, 1)T θS3(i, j, k) T = (0, i, 2, j, 0, k, 0)T θS4(i, j) T = (0, i, 2, j, 1)T
onde θT ´e a matriz transposta de θ. Para entender sua constru¸c˜ao, ´e necess´aria a seguinte
defini¸c˜ao:
Defini¸c˜ao 9 (Passeios em ´arvores). Dada uma ´arvore T, um passeio ´e qualquer
3.1. Conceitos Alg´ebricos 18
v´ertices s˜ao adjacentes se, e s´o se, s˜ao v´ertices consecutivos nesta lista, e mediados pela
aresta que os liga. Para o caso espec´ıfico de ´arvores, todo passeio ´e tamb´em chamado de
caminho (passeio que n˜ao repete v´ertices), uma vez que ´arvores n˜ao possuem ciclos.
Assim sendo, para construir a fun¸c˜ao de escalonamento para S1, por exemplo,
realiza-se o pasrealiza-seio da raiz at´e a folha que representa esta senten¸ca na LST. O escalonamento
para a dada senten¸ca, denotado por θS1, ´e a lista obtida para tal passeio, que (note) ´e
dada em fun¸c˜ao dos n´os i e j. De maneira semelhante, podem-se descrever as fun¸c˜oes de
escalonamento para todas as demais senten¸cas pertencentes a esta ´arvore.
Por constru¸c˜ao, perceba que nenhum v´ertice de uma LST possui mais que um caminho
at´e a raiz: primeiro porque, por defini¸c˜ao, n˜ao existem ciclos em ´arvores; segundo,
ne-nhuma senten¸ca (v´ertices mais externos da ´arvore, ou simplesmente folhas) aparece mais
de uma vez na ´arvore. Ent˜ao, a fun¸c˜ao de escalonamento de um v´ertice que representa
uma senten¸ca S ´e dada pelo caminho da raiz da LST at´e a folha que representa a dada
senten¸ca.
Essas fun¸c˜oes de escalonamento dependem das vari´aveis de indu¸c˜ao, e d˜ao, para cada
senten¸ca, uma data de execu¸c˜ao ´unica. Gra¸cas a isso, ´e poss´ıvel gerar c´odigo equivalente
ao original a partir de uma representa¸c˜ao de pol´ıtopo.
Al´em disso, as fun¸c˜oes de escalonamento s˜ao fundamentais para a reestrutura¸c˜ao de
c´odigo. ´E atrav´es delas que otimiza¸c˜oes ou a pr´opria paraleliza¸c˜ao do c´odigo s˜ao
im-plementadas: s˜ao essas fun¸c˜oes que ditam a ordem a seguir para percorrer o conjunto
de ´ındices. Mas ´e importante perceber que nem todo escalonamento para um conjunto
de ´ındices ´e v´alido: somente os escalonamentos que respeitam as dependˆencias entre as
senten¸cas pertencentes ao la¸co. Formalmente, ´e necess´ario ao escalonamento respeitar `a
seguinte condi¸c˜ao de casualidade:
Defini¸c˜ao 10 (Condi¸c˜ao de casualidade). Um escalonamento deve obedecer ao
se-guinte princ´ıpio de casualidade, que estabelece uma rela¸c˜ao entre dependˆencias e
escalo-namentos:
(∀u, v ∈ Ω : u δ v ⇒ θ(u) + 1 ≤ θ(v)).
Onde Ω ´e o conjunto de todas as opera¸c˜oes7 de uma senten¸ca, e u δ v representa
uma dependˆencia de v para com u8. Em outras palavras, se alguma instˆancia de uma
senten¸ca depende de uma instˆancia de outra senten¸ca, o escalonamento da segunda deve
ser anterior ao da primeira para preservar o sentido desta dependˆencia.
7Uma opera¸c˜ao pode ser vista como uma instˆancia em tempo de execu¸c˜ao de uma senten¸ca. 8Cf. defini¸c˜ao 11.
3.2. Alcan¸cando o Paralelismo 19
3.2
Alcan¸
cando o Paralelismo
´
E trabalho do compilador transformar uma computa¸c˜ao de uma representa¸c˜ao em alto
n´ıvel, que ´e f´acil para um humano entender, para uma representa¸c˜ao em baixo n´ıvel, que
a m´aquina pode executar. Um ponto cr´ıtico ´e que a representa¸c˜ao humana raramente
comporta detalhes sobre a arquitetura da m´aquina que executar´a o programa, for¸cando
a introdu¸c˜ao de trechos ineficientes de c´odigo em linguagens de alto-n´ıvel. A fim de
eliminar essa ineficiˆencia do c´odigo, fases de otimiza¸c˜ao do c´odigo foram introduzidas aos
compiladores.
Otimiza¸c˜oes tˆem sido desenvolvidas para alcan¸car o m´aximo desempenho, apesar das
diferen¸cas nas arquiteturas dos computadores – entre escalares, superescalares, vetoriais,
etc. Como consequˆencia natural, compiladores tamb´em deveriam ser respons´aveis por
transformar o c´odigo automaticamente para que o programa resultante tenha vantagens
sobre m´aquinas paralelas; afinal, o compilador ´e livre para transformar o c´odigo, desde
que o programa gerado compute os mesmos resultados que o original.
Mas as otimiza¸c˜oes – e, na maioria das vezes, a combina¸c˜ao delas – que permitem
explorar o paralelismo s˜ao demasiado radicais. Por exemplo, para alcan¸car desempenho
´
otimo num algoritmo de multiplica¸c˜ao de matrizes, ´e necess´ario realizar loop interchange,
loop splitting, loop distribution, vetoriza¸c˜ao e, enfim, paraleliza¸c˜ao [29]. Metade dessas
transforma¸c˜oes nem ao menos s˜ao encontradas na maioria dos compiladores.
Como visto na se¸c˜ao anterior, as transforma¸c˜oes (e, em especial, as que exploram
paralelismo) modificam a ordem de execu¸c˜ao das instru¸c˜oes do programa. O principal
desafio que um compilador deve enfrentar ´e justamente determinar quando dada ordem de
execu¸c˜ao ´e v´alida. Em outras palavras, o compilador deve conhecer, e respeitar, restri¸c˜oes
que garantam que o c´odigo sempre ir´a computar os mesmos resultados do c´odigo original.
Dado que tais restri¸c˜oes limitam as transforma¸c˜oes, a chave ´e encontrar um conjunto
m´ınimo de restri¸c˜oes que garantam que o c´odigo transformado ir´a gerar o resultado correto.
Nesta disserta¸c˜ao, usaremos um conjunto de restri¸c˜oes denominadas dependˆencias.
Defini¸c˜ao 11 (Dependˆencia). Dependˆencia pode ser vista como uma rela¸c˜ao entre as
instru¸c˜oes de um programa. O par hS1, S2i ´e uma rela¸c˜ao de dependˆencia se S1 deve
executar antes de S2 em qualquer reordenamento v´alido das instru¸c˜oes. Comumente,
denota-se por S1 δ S2, para duas senten¸cas S1 e S2no c´odigo, representando a dependˆencia
de S2 para com S1.
Para ilustrar esta defini¸c˜ao, considere o fragmento de c´odigo a seguir.
Para este c´odigo, os resultados originais est˜ao definidos como aqueles gerados quando
3.2. Alcan¸cando o Paralelismo 20
Algoritmo 3.4: Trecho de c´odigo que ilustra dependˆencias de dados e rela¸c˜oes de
ordem entre essas dependˆencias.
S1: pi ← 3.14159
S2: r ← 5
S3: area ← pi ∗ r2
hS2,S1,S3i produz o mesmo resultado (o mesmo valor de ´area ´e calculado). As
de-pendˆencias presentes em S3 para as duas instru¸c˜oes anteriores, por´em, impedem que
esta seja executada em qualquer outro momento.
Apesar de ser um conceito f´acil de identificar em trechos mais simples (sem desvios),
a otimiza¸c˜ao de tais trechos n˜ao garante a utiliza¸c˜ao efetiva dos recursos em m´aquinas
paralelas: o conceito de dependˆencia deve ser aplicado a fragmentos mais complexos,
como corpos de la¸co, ou chamadas a fun¸c˜oes, por exemplo. Nesses ambientes, entretanto,
podem ocorrer rela¸c˜oes de dependˆencia muito mais dif´ıceis de serem manipuladas. Um
exemplo est´a mostrado no fragmento de c´odigo exibido no algoritmo 3.5.
Algoritmo 3.5: Trecho de c´odigo que ilustra dependˆencia c´ıclica de dados.
for i ← 1 to N do
S1: A[i] ← B[i] + 1
S2: B[i + 1] ← A[i] − 5
end
Neste algoritmo, existe uma dependˆencia que for¸ca a ordem hS1,S2i, porque todo
elemento de A computado pela primeira senten¸ca ´e imediatamente utilizado pela segunda
senten¸ca, e tamb´em existe uma outra rela¸c˜ao do tipo hS2,S1i, uma vez que toda itera¸c˜ao
do la¸co usa o valor do elemento em B calculado na itera¸c˜ao anterior. A este tipo de
dependˆencia, d´a-se o nome de dependˆencia c´ıclica. ´E importante notar que dependˆencias
c´ıclicas tamb´em podem ocorrer numa instru¸c˜ao sobre ela mesma, como ser´a discutido no
algoritmo 3.7.
3.2.1
An´
alise das Dependˆ
encias
Otimiza¸c˜oes de c´odigo, em especial as mais sofisticadas, introduzem alguma complexidade
ao processo de compila¸c˜ao; a paraleliza¸c˜ao autom´atica introduz muito mais complexidade
a este processo. Para computadores paralelos, encontrar paralelismo em c´odigo sequencial
´e, por si s´o, uma otimiza¸c˜ao. Para auxiliar nessa tarefa, as dependˆencias no c´odigo do
programa s˜ao identificadas e as otimiza¸c˜oes s˜ao ajustadas para preservar o sentido dessas
3.2. Alcan¸cando o Paralelismo 21
Defini¸c˜ao 12 (Teorema Fundamental de Dependˆencia). Qualquer transforma¸c˜ao
que reordena senten¸cas preservando toda dependˆencia no programa preserva tamb´em o
significado do programa.
A prova do teorema acima pode ser visto em [29]. Como consequˆencia direta deste
teo-rema, qualquer transforma¸c˜ao que busque paralelismo por reescalonamento de instru¸c˜oes ´e
v´alida, desde que preserve o sentido das dependˆencias. Ao usar as dependˆencias do c´odigo
como base para transforma¸c˜oes, ent˜ao, sua identifica¸c˜ao e an´alise s˜ao pontos cr´ıticos:
de-vem prover informa¸c˜oes suficientes para descobrir se (e como) alguma transforma¸c˜ao pode
ser aplicada.
Dependˆencias representam dois diferentes tipos de restri¸c˜oes para transforma¸c˜oes no
c´odigo: restri¸c˜oes que permitem ou n˜ao que instru¸c˜oes executem baseadas na execu¸c˜ao de
outras instru¸c˜oes, conhecidas por dependˆencias de controle; e restri¸c˜oes que garantem que
dados sejam consumidos e produzidos em determinada ordem, chamadas de dependˆencias
de dados. Este trabalho concentra-se apenas no ´ultimo tipo de dependˆencias, visto que, no
caso geral, paralelismo ´e encontrado ao aplicar decomposi¸c˜ao de dados9. As dependˆencias
de controle, portanto, ser˜ao mantidas pelas vers˜oes das transforma¸c˜oes discutidas aqui10.
Formalmente,
Defini¸c˜ao 13 (Dependˆencia de dados). Existe dependˆencia de dados da senten¸ca S1
para S2 (ou seja, a senten¸ca S2 depende de S1) se, e somente se, (1) ambas as senten¸cas
acessam a mesma posi¸c˜ao de mem´oria e, no m´ınimo, uma delas realiza uma escrita e (2)
h´a um caminho poss´ıvel entre S1 e S2 durante a execu¸c˜ao.
Note que instru¸c˜oes que realizam leitura de mesmos dados n˜ao caracterizam
de-pendˆencia de dados: uma vez que a leitura n˜ao modifica dados na mem´oria, a ordem
entre duas ou mais leituras pode ser alterada sem modificar a semˆantica do programa.
Logo, pode existir uma dependˆencia de dados entre duas instru¸c˜oes, sejam S1, S2 e S1 δ
S2, se S1 escreve em um dado que S2 ler´a (chamada dependˆencia verdadeira), se S1 lˆe
de um dado que ser´a atualizado por S2 (antidependˆencia), ou se S1 escreve num dado
que ser´a sobrescrito por S2 (dependˆencia de sa´ıda). Ademais, duas instru¸c˜oes que n˜ao
9T´ecnica na qual v´arias tarefas paralelas executam opera¸c˜oes similares sobre conjuntos diferentes de
dados.
10Com algumas modifica¸c˜oes, as transforma¸c˜oes podem passar a manipular certos tipos de dependˆencias
de controle. Al´em disso, algumas dessas dependˆencias podem ser traduzidas em dependˆencias de dados, o que permitiria o uso das transforma¸c˜oes como s˜ao mostradas aqui; mas essas modifica¸c˜oes n˜ao ser˜ao discutidas neste trabalho. Dessa forma, qualquer cita¸c˜ao a dependˆencias no texto a partir daqui, faz referˆencia `a dependˆencia de dado (exceto quando explicitado). Para maiores detalhes sobre tratamento de dependˆencias de controle, consultar o cap´ıtulo 7 da referˆencia [29].