um estudo comparativo de linguagens funcionais para ...tg/2012-2/lgnfl.pdf · os processadores...

40
Universidade Federal de Pernambuco Centro de Informática Graduação em Ciência da Computação Um Estudo Comparativo de Linguagens Funcionais para Implementar Sistemas Concorrentes Luís Gabriel Nunes Ferreira Lima Trabalho de Graduação Recife 25 de abril de 2013

Upload: others

Post on 15-Aug-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

Universidade Federal de PernambucoCentro de Informaacutetica

Graduaccedilatildeo em Ciecircncia da Computaccedilatildeo

Um Estudo Comparativo de LinguagensFuncionais para Implementar Sistemas

Concorrentes

Luiacutes Gabriel Nunes Ferreira Lima

Trabalho de Graduaccedilatildeo

Recife25 de abril de 2013

Universidade Federal de PernambucoCentro de Informaacutetica

Luiacutes Gabriel Nunes Ferreira Lima

Um Estudo Comparativo de Linguagens Funcionais paraImplementar Sistemas Concorrentes

Trabalho apresentado ao Programa de Graduaccedilatildeo emCiecircncia da Computaccedilatildeo do Centro de Informaacutetica da Uni-versidade Federal de Pernambuco como requisito parcialpara obtenccedilatildeo do grau de Bacharel em Ciecircncia da Com-putaccedilatildeo

Orientador Fernando Joseacute Castor de Lima Filho

Recife25 de abril de 2013

The only way to make sense out of change is to plunge into it move with itand join the dancemdashALAN WATTS

Agradecimentos

Primeiramente para ser justo preciso agradecer agrave minha famiacutelia Aos meus pais Algeny eAlcides muito obrigado pelas oacutetimas condiccedilotildees que vocecircs me proporcionaram durante todaminha vida Muito obrigado pela paciecircncia pelo amor e pelo carinho que vocecircs tem comigoAgradeccedilo tambeacutem agrave minha irmatilde Isabela pelo carinho e pelo zelo que vocecirc tem por mim Semo apoio de vocecircs eu natildeo teria chegado ateacute aqui Muito obrigado tambeacutem pela compreensatildeodurante os vaacuterios finais de semana que estive ausente devido agrave compromissos da faculdade

Gostaria de agradecer aos meus amigos Claacuteudio (Rabicoacute) Gustavo Lucas (Tibuta) MarcelaMarina (Nina) e Tiago (Rato) pelas conversas pelas viagens pelas risadas e pelos vaacuterios mo-mentos que compartilhamos juntos Vocecircs satildeo pessoas fantaacutesticas De formas diferentes cadaum de vocecircs contribiu muito para formaccedilatildeo do meu inteleacutecto e do meu caraacuteter Agradeccedilo tam-beacutem a meu amigo Rafael (Bobinho) pelas oacutetimas conversas regadas ao bom e velho blues naPraccedila de Casa Forte

Agradeccedilo tambeacutem agrave Marcelo Jensen por sempre consertar as besteiras que eu fazia no com-putador de casa quando eu era mais novo Sua paciecircncia em sempre me explicar o que estavaacontecendo e o porque das coisas natildeo estarem funcionando como deveriam foram fundamen-tais para despertar em mim o interesse pela computaccedilatildeo

Agradeccedilo agrave Bruno Coelho Crystal Liacutevia e Tiago por terem sido mais que amigos duranteesse periacuteodo da graduaccedilatildeo Agradeccedilo agrave Bruno Medeiros Henrique Helder e Jonathas pelaamizade e pela companhia durante os vaacuterios projetos que fizemos juntos Agradeccedilo tambeacutemaos demais colegas de turma de CC e EC pela convivecircncia durante esse periacuteodo sem todosvocecircs teria sido muito mais difiacutecil

Agradeccedilo ao meu orientador Fernando Castor pelo apoio e pela inspiraccedilatildeo profissionaldurante esses quase trecircs anos nos quais fui seu aluno monitor e orientando Agradeccedilo aoprofessor Ricardo Massa pela confianccedila depositada em mim durante a monitoria de Introduccedilatildeoagrave Programaccedilatildeo Agradeccedilo ao professor Paulo Gonccedilalves pelos ensinamentos valiosos duranteo periacuteodo de iniciaccedilatildeo cientiacutefica Agradeccedilo tambeacutem a todo corpo docente do CIn pelo empenhoe dedicaccedilatildeo que foram fundamentais para minha formaccedilatildeo acadecircmica e pessoal

Por fim agradeccedilo ao Instituto Nokia de Tecnologia (INdT) por ser um lugar fantaacutesticode se trabalhar Muito obrigado agraves vaacuterias pessoas com que tive o prazer de trabalhar duranteesses dois anos de INdT e que hoje servem de inspiraccedilatildeo pessoal e profissional para mim Emespecial gostaria de agradecer agrave Anselmo Daker Lacerda Figueredo Mailson Adriano eCidorvan pelas vaacuterias discussotildees teacutecnicas e pelas conversas aleatoacuterias durante o almoccedilo

iv

Resumo

Os processadores multicore estatildeo presentes em praticamente todos os computadores modernosinclusive em dispositivos moacuteveis como telefones celular e tablets Poreacutem pouco desse poderde processamento provido pelos muacuteltiplos nuacutecleos eacute aproveitado de maneira efetiva pelas apli-caccedilotildees devido agrave dificuldade de se escrever sistemas concorrentes Com o objetivo de tornar odesenvolvimento desse tipo de sistema mais palpaacutevel alguns novos mecanismos de sincroniza-ccedilatildeo e paralelismo vem sendo propostos em linguagens de programaccedilatildeo funcional Esse tipode linguagem prega um estilo de programaccedilatildeo baseado em funccedilotildees puras e dados imutaacuteveisque facilita o desenvolvimento de programas concorrentes Com o objetivo de entender mel-hor esses benefiacutecios este trabalho faz um estudo comparativo entre as linguagens funcionaisClojure e Haskell com foco na utilizaccedilatildeo de Memoacuteria Transacional em Software Para isso foiutilizado como objeto de estudo a implementaccedilatildeo de um motor de busca paralelo em ambas aslinguagens

Palavras-chave memoacuteria transacional em sotware programaccedilatildeo funcional concorrecircnciamotor de busca recuperaccedilatildeo de informaccedilatildeo

v

Abstract

The multicore processors are present in almost every modern computer including mobile de-vices like smartphones and tablets However only a small portion of the processing powerprovided by the multiple cores are actually used by the applications due the difficulty of writ-ing concurrent systems Aiming to make the development of this kind of system become moretangible some new synchronization mecanisms are being proposed in functional programminglanguages Functional programming emphasizes a programming style based on pure functionsand immutable data which makes the development of concurrent programs easier The aim ofthis project is to make a comparative study of the functional languages Clojure and Haskellfocusing on the use of Sotfware Transacional Memory To this end the implementation of aparallel search engine was used as object of study

Keywords software transacional memory functional programming concurrency search en-gine information retrieval

vi

Sumaacuterio

1 Introduccedilatildeo 1

2 Memoacuteria Transacional em Software 321 Transaccedilotildees 322 Bloco atocircmico 423 Composabilidade 424 Controle de Concorrecircncia 625 Estrateacutegias de Atualizaccedilatildeo e Versionamento 626 Outros Aspectos 7

3 Clojure 831 Conceitos baacutesicos 832 Interoperabilidade com Java 933 Threads 934 Memoacuteria Transacional em Clojure 10

341 Controle de Concorrecircncia 11342 Variaacuteveis transacionais 11343 Blocos atocircmicos 11344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 12

4 Haskell 1341 Conceitos baacutesicos 1342 Monads 1443 Threads 1544 Memoacuteria Transacional em Haskell 15

441 Variaacuteveis transacionais 15442 Bloco atocircmico 16443 Bloqueio e escolha 16444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 17

5 Um Motor de Busca Paralelo com STM 1851 Especificaccedilatildeo 1852 Versatildeo sequencial 19

521 Iacutendice 19522 Arquitetura 20

53 Versatildeo paralela 21

vii

SUMAacuteRIO viii

531 Iacutendice 21532 Arquitetura 22

6 Resultados 2461 Comparaccedilatildeo das implementaccedilotildees 2462 Experimentos 26

7 Conclusatildeo 29

Lista de Figuras

51 Representaccedilatildeo do iacutendice 2052 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial 2053 Representaccedilatildeo das duas abordagens de particionamento de iacutendice 2254 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads 23

61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell 2662 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas 2763 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo 2864 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo 28

ix

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 2: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

Universidade Federal de PernambucoCentro de Informaacutetica

Luiacutes Gabriel Nunes Ferreira Lima

Um Estudo Comparativo de Linguagens Funcionais paraImplementar Sistemas Concorrentes

Trabalho apresentado ao Programa de Graduaccedilatildeo emCiecircncia da Computaccedilatildeo do Centro de Informaacutetica da Uni-versidade Federal de Pernambuco como requisito parcialpara obtenccedilatildeo do grau de Bacharel em Ciecircncia da Com-putaccedilatildeo

Orientador Fernando Joseacute Castor de Lima Filho

Recife25 de abril de 2013

The only way to make sense out of change is to plunge into it move with itand join the dancemdashALAN WATTS

Agradecimentos

Primeiramente para ser justo preciso agradecer agrave minha famiacutelia Aos meus pais Algeny eAlcides muito obrigado pelas oacutetimas condiccedilotildees que vocecircs me proporcionaram durante todaminha vida Muito obrigado pela paciecircncia pelo amor e pelo carinho que vocecircs tem comigoAgradeccedilo tambeacutem agrave minha irmatilde Isabela pelo carinho e pelo zelo que vocecirc tem por mim Semo apoio de vocecircs eu natildeo teria chegado ateacute aqui Muito obrigado tambeacutem pela compreensatildeodurante os vaacuterios finais de semana que estive ausente devido agrave compromissos da faculdade

Gostaria de agradecer aos meus amigos Claacuteudio (Rabicoacute) Gustavo Lucas (Tibuta) MarcelaMarina (Nina) e Tiago (Rato) pelas conversas pelas viagens pelas risadas e pelos vaacuterios mo-mentos que compartilhamos juntos Vocecircs satildeo pessoas fantaacutesticas De formas diferentes cadaum de vocecircs contribiu muito para formaccedilatildeo do meu inteleacutecto e do meu caraacuteter Agradeccedilo tam-beacutem a meu amigo Rafael (Bobinho) pelas oacutetimas conversas regadas ao bom e velho blues naPraccedila de Casa Forte

Agradeccedilo tambeacutem agrave Marcelo Jensen por sempre consertar as besteiras que eu fazia no com-putador de casa quando eu era mais novo Sua paciecircncia em sempre me explicar o que estavaacontecendo e o porque das coisas natildeo estarem funcionando como deveriam foram fundamen-tais para despertar em mim o interesse pela computaccedilatildeo

Agradeccedilo agrave Bruno Coelho Crystal Liacutevia e Tiago por terem sido mais que amigos duranteesse periacuteodo da graduaccedilatildeo Agradeccedilo agrave Bruno Medeiros Henrique Helder e Jonathas pelaamizade e pela companhia durante os vaacuterios projetos que fizemos juntos Agradeccedilo tambeacutemaos demais colegas de turma de CC e EC pela convivecircncia durante esse periacuteodo sem todosvocecircs teria sido muito mais difiacutecil

Agradeccedilo ao meu orientador Fernando Castor pelo apoio e pela inspiraccedilatildeo profissionaldurante esses quase trecircs anos nos quais fui seu aluno monitor e orientando Agradeccedilo aoprofessor Ricardo Massa pela confianccedila depositada em mim durante a monitoria de Introduccedilatildeoagrave Programaccedilatildeo Agradeccedilo ao professor Paulo Gonccedilalves pelos ensinamentos valiosos duranteo periacuteodo de iniciaccedilatildeo cientiacutefica Agradeccedilo tambeacutem a todo corpo docente do CIn pelo empenhoe dedicaccedilatildeo que foram fundamentais para minha formaccedilatildeo acadecircmica e pessoal

Por fim agradeccedilo ao Instituto Nokia de Tecnologia (INdT) por ser um lugar fantaacutesticode se trabalhar Muito obrigado agraves vaacuterias pessoas com que tive o prazer de trabalhar duranteesses dois anos de INdT e que hoje servem de inspiraccedilatildeo pessoal e profissional para mim Emespecial gostaria de agradecer agrave Anselmo Daker Lacerda Figueredo Mailson Adriano eCidorvan pelas vaacuterias discussotildees teacutecnicas e pelas conversas aleatoacuterias durante o almoccedilo

iv

Resumo

Os processadores multicore estatildeo presentes em praticamente todos os computadores modernosinclusive em dispositivos moacuteveis como telefones celular e tablets Poreacutem pouco desse poderde processamento provido pelos muacuteltiplos nuacutecleos eacute aproveitado de maneira efetiva pelas apli-caccedilotildees devido agrave dificuldade de se escrever sistemas concorrentes Com o objetivo de tornar odesenvolvimento desse tipo de sistema mais palpaacutevel alguns novos mecanismos de sincroniza-ccedilatildeo e paralelismo vem sendo propostos em linguagens de programaccedilatildeo funcional Esse tipode linguagem prega um estilo de programaccedilatildeo baseado em funccedilotildees puras e dados imutaacuteveisque facilita o desenvolvimento de programas concorrentes Com o objetivo de entender mel-hor esses benefiacutecios este trabalho faz um estudo comparativo entre as linguagens funcionaisClojure e Haskell com foco na utilizaccedilatildeo de Memoacuteria Transacional em Software Para isso foiutilizado como objeto de estudo a implementaccedilatildeo de um motor de busca paralelo em ambas aslinguagens

Palavras-chave memoacuteria transacional em sotware programaccedilatildeo funcional concorrecircnciamotor de busca recuperaccedilatildeo de informaccedilatildeo

v

Abstract

The multicore processors are present in almost every modern computer including mobile de-vices like smartphones and tablets However only a small portion of the processing powerprovided by the multiple cores are actually used by the applications due the difficulty of writ-ing concurrent systems Aiming to make the development of this kind of system become moretangible some new synchronization mecanisms are being proposed in functional programminglanguages Functional programming emphasizes a programming style based on pure functionsand immutable data which makes the development of concurrent programs easier The aim ofthis project is to make a comparative study of the functional languages Clojure and Haskellfocusing on the use of Sotfware Transacional Memory To this end the implementation of aparallel search engine was used as object of study

Keywords software transacional memory functional programming concurrency search en-gine information retrieval

vi

Sumaacuterio

1 Introduccedilatildeo 1

2 Memoacuteria Transacional em Software 321 Transaccedilotildees 322 Bloco atocircmico 423 Composabilidade 424 Controle de Concorrecircncia 625 Estrateacutegias de Atualizaccedilatildeo e Versionamento 626 Outros Aspectos 7

3 Clojure 831 Conceitos baacutesicos 832 Interoperabilidade com Java 933 Threads 934 Memoacuteria Transacional em Clojure 10

341 Controle de Concorrecircncia 11342 Variaacuteveis transacionais 11343 Blocos atocircmicos 11344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 12

4 Haskell 1341 Conceitos baacutesicos 1342 Monads 1443 Threads 1544 Memoacuteria Transacional em Haskell 15

441 Variaacuteveis transacionais 15442 Bloco atocircmico 16443 Bloqueio e escolha 16444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 17

5 Um Motor de Busca Paralelo com STM 1851 Especificaccedilatildeo 1852 Versatildeo sequencial 19

521 Iacutendice 19522 Arquitetura 20

53 Versatildeo paralela 21

vii

SUMAacuteRIO viii

531 Iacutendice 21532 Arquitetura 22

6 Resultados 2461 Comparaccedilatildeo das implementaccedilotildees 2462 Experimentos 26

7 Conclusatildeo 29

Lista de Figuras

51 Representaccedilatildeo do iacutendice 2052 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial 2053 Representaccedilatildeo das duas abordagens de particionamento de iacutendice 2254 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads 23

61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell 2662 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas 2763 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo 2864 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo 28

ix

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 3: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

The only way to make sense out of change is to plunge into it move with itand join the dancemdashALAN WATTS

Agradecimentos

Primeiramente para ser justo preciso agradecer agrave minha famiacutelia Aos meus pais Algeny eAlcides muito obrigado pelas oacutetimas condiccedilotildees que vocecircs me proporcionaram durante todaminha vida Muito obrigado pela paciecircncia pelo amor e pelo carinho que vocecircs tem comigoAgradeccedilo tambeacutem agrave minha irmatilde Isabela pelo carinho e pelo zelo que vocecirc tem por mim Semo apoio de vocecircs eu natildeo teria chegado ateacute aqui Muito obrigado tambeacutem pela compreensatildeodurante os vaacuterios finais de semana que estive ausente devido agrave compromissos da faculdade

Gostaria de agradecer aos meus amigos Claacuteudio (Rabicoacute) Gustavo Lucas (Tibuta) MarcelaMarina (Nina) e Tiago (Rato) pelas conversas pelas viagens pelas risadas e pelos vaacuterios mo-mentos que compartilhamos juntos Vocecircs satildeo pessoas fantaacutesticas De formas diferentes cadaum de vocecircs contribiu muito para formaccedilatildeo do meu inteleacutecto e do meu caraacuteter Agradeccedilo tam-beacutem a meu amigo Rafael (Bobinho) pelas oacutetimas conversas regadas ao bom e velho blues naPraccedila de Casa Forte

Agradeccedilo tambeacutem agrave Marcelo Jensen por sempre consertar as besteiras que eu fazia no com-putador de casa quando eu era mais novo Sua paciecircncia em sempre me explicar o que estavaacontecendo e o porque das coisas natildeo estarem funcionando como deveriam foram fundamen-tais para despertar em mim o interesse pela computaccedilatildeo

Agradeccedilo agrave Bruno Coelho Crystal Liacutevia e Tiago por terem sido mais que amigos duranteesse periacuteodo da graduaccedilatildeo Agradeccedilo agrave Bruno Medeiros Henrique Helder e Jonathas pelaamizade e pela companhia durante os vaacuterios projetos que fizemos juntos Agradeccedilo tambeacutemaos demais colegas de turma de CC e EC pela convivecircncia durante esse periacuteodo sem todosvocecircs teria sido muito mais difiacutecil

Agradeccedilo ao meu orientador Fernando Castor pelo apoio e pela inspiraccedilatildeo profissionaldurante esses quase trecircs anos nos quais fui seu aluno monitor e orientando Agradeccedilo aoprofessor Ricardo Massa pela confianccedila depositada em mim durante a monitoria de Introduccedilatildeoagrave Programaccedilatildeo Agradeccedilo ao professor Paulo Gonccedilalves pelos ensinamentos valiosos duranteo periacuteodo de iniciaccedilatildeo cientiacutefica Agradeccedilo tambeacutem a todo corpo docente do CIn pelo empenhoe dedicaccedilatildeo que foram fundamentais para minha formaccedilatildeo acadecircmica e pessoal

Por fim agradeccedilo ao Instituto Nokia de Tecnologia (INdT) por ser um lugar fantaacutesticode se trabalhar Muito obrigado agraves vaacuterias pessoas com que tive o prazer de trabalhar duranteesses dois anos de INdT e que hoje servem de inspiraccedilatildeo pessoal e profissional para mim Emespecial gostaria de agradecer agrave Anselmo Daker Lacerda Figueredo Mailson Adriano eCidorvan pelas vaacuterias discussotildees teacutecnicas e pelas conversas aleatoacuterias durante o almoccedilo

iv

Resumo

Os processadores multicore estatildeo presentes em praticamente todos os computadores modernosinclusive em dispositivos moacuteveis como telefones celular e tablets Poreacutem pouco desse poderde processamento provido pelos muacuteltiplos nuacutecleos eacute aproveitado de maneira efetiva pelas apli-caccedilotildees devido agrave dificuldade de se escrever sistemas concorrentes Com o objetivo de tornar odesenvolvimento desse tipo de sistema mais palpaacutevel alguns novos mecanismos de sincroniza-ccedilatildeo e paralelismo vem sendo propostos em linguagens de programaccedilatildeo funcional Esse tipode linguagem prega um estilo de programaccedilatildeo baseado em funccedilotildees puras e dados imutaacuteveisque facilita o desenvolvimento de programas concorrentes Com o objetivo de entender mel-hor esses benefiacutecios este trabalho faz um estudo comparativo entre as linguagens funcionaisClojure e Haskell com foco na utilizaccedilatildeo de Memoacuteria Transacional em Software Para isso foiutilizado como objeto de estudo a implementaccedilatildeo de um motor de busca paralelo em ambas aslinguagens

Palavras-chave memoacuteria transacional em sotware programaccedilatildeo funcional concorrecircnciamotor de busca recuperaccedilatildeo de informaccedilatildeo

v

Abstract

The multicore processors are present in almost every modern computer including mobile de-vices like smartphones and tablets However only a small portion of the processing powerprovided by the multiple cores are actually used by the applications due the difficulty of writ-ing concurrent systems Aiming to make the development of this kind of system become moretangible some new synchronization mecanisms are being proposed in functional programminglanguages Functional programming emphasizes a programming style based on pure functionsand immutable data which makes the development of concurrent programs easier The aim ofthis project is to make a comparative study of the functional languages Clojure and Haskellfocusing on the use of Sotfware Transacional Memory To this end the implementation of aparallel search engine was used as object of study

Keywords software transacional memory functional programming concurrency search en-gine information retrieval

vi

Sumaacuterio

1 Introduccedilatildeo 1

2 Memoacuteria Transacional em Software 321 Transaccedilotildees 322 Bloco atocircmico 423 Composabilidade 424 Controle de Concorrecircncia 625 Estrateacutegias de Atualizaccedilatildeo e Versionamento 626 Outros Aspectos 7

3 Clojure 831 Conceitos baacutesicos 832 Interoperabilidade com Java 933 Threads 934 Memoacuteria Transacional em Clojure 10

341 Controle de Concorrecircncia 11342 Variaacuteveis transacionais 11343 Blocos atocircmicos 11344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 12

4 Haskell 1341 Conceitos baacutesicos 1342 Monads 1443 Threads 1544 Memoacuteria Transacional em Haskell 15

441 Variaacuteveis transacionais 15442 Bloco atocircmico 16443 Bloqueio e escolha 16444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 17

5 Um Motor de Busca Paralelo com STM 1851 Especificaccedilatildeo 1852 Versatildeo sequencial 19

521 Iacutendice 19522 Arquitetura 20

53 Versatildeo paralela 21

vii

SUMAacuteRIO viii

531 Iacutendice 21532 Arquitetura 22

6 Resultados 2461 Comparaccedilatildeo das implementaccedilotildees 2462 Experimentos 26

7 Conclusatildeo 29

Lista de Figuras

51 Representaccedilatildeo do iacutendice 2052 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial 2053 Representaccedilatildeo das duas abordagens de particionamento de iacutendice 2254 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads 23

61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell 2662 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas 2763 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo 2864 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo 28

ix

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 4: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

Agradecimentos

Primeiramente para ser justo preciso agradecer agrave minha famiacutelia Aos meus pais Algeny eAlcides muito obrigado pelas oacutetimas condiccedilotildees que vocecircs me proporcionaram durante todaminha vida Muito obrigado pela paciecircncia pelo amor e pelo carinho que vocecircs tem comigoAgradeccedilo tambeacutem agrave minha irmatilde Isabela pelo carinho e pelo zelo que vocecirc tem por mim Semo apoio de vocecircs eu natildeo teria chegado ateacute aqui Muito obrigado tambeacutem pela compreensatildeodurante os vaacuterios finais de semana que estive ausente devido agrave compromissos da faculdade

Gostaria de agradecer aos meus amigos Claacuteudio (Rabicoacute) Gustavo Lucas (Tibuta) MarcelaMarina (Nina) e Tiago (Rato) pelas conversas pelas viagens pelas risadas e pelos vaacuterios mo-mentos que compartilhamos juntos Vocecircs satildeo pessoas fantaacutesticas De formas diferentes cadaum de vocecircs contribiu muito para formaccedilatildeo do meu inteleacutecto e do meu caraacuteter Agradeccedilo tam-beacutem a meu amigo Rafael (Bobinho) pelas oacutetimas conversas regadas ao bom e velho blues naPraccedila de Casa Forte

Agradeccedilo tambeacutem agrave Marcelo Jensen por sempre consertar as besteiras que eu fazia no com-putador de casa quando eu era mais novo Sua paciecircncia em sempre me explicar o que estavaacontecendo e o porque das coisas natildeo estarem funcionando como deveriam foram fundamen-tais para despertar em mim o interesse pela computaccedilatildeo

Agradeccedilo agrave Bruno Coelho Crystal Liacutevia e Tiago por terem sido mais que amigos duranteesse periacuteodo da graduaccedilatildeo Agradeccedilo agrave Bruno Medeiros Henrique Helder e Jonathas pelaamizade e pela companhia durante os vaacuterios projetos que fizemos juntos Agradeccedilo tambeacutemaos demais colegas de turma de CC e EC pela convivecircncia durante esse periacuteodo sem todosvocecircs teria sido muito mais difiacutecil

Agradeccedilo ao meu orientador Fernando Castor pelo apoio e pela inspiraccedilatildeo profissionaldurante esses quase trecircs anos nos quais fui seu aluno monitor e orientando Agradeccedilo aoprofessor Ricardo Massa pela confianccedila depositada em mim durante a monitoria de Introduccedilatildeoagrave Programaccedilatildeo Agradeccedilo ao professor Paulo Gonccedilalves pelos ensinamentos valiosos duranteo periacuteodo de iniciaccedilatildeo cientiacutefica Agradeccedilo tambeacutem a todo corpo docente do CIn pelo empenhoe dedicaccedilatildeo que foram fundamentais para minha formaccedilatildeo acadecircmica e pessoal

Por fim agradeccedilo ao Instituto Nokia de Tecnologia (INdT) por ser um lugar fantaacutesticode se trabalhar Muito obrigado agraves vaacuterias pessoas com que tive o prazer de trabalhar duranteesses dois anos de INdT e que hoje servem de inspiraccedilatildeo pessoal e profissional para mim Emespecial gostaria de agradecer agrave Anselmo Daker Lacerda Figueredo Mailson Adriano eCidorvan pelas vaacuterias discussotildees teacutecnicas e pelas conversas aleatoacuterias durante o almoccedilo

iv

Resumo

Os processadores multicore estatildeo presentes em praticamente todos os computadores modernosinclusive em dispositivos moacuteveis como telefones celular e tablets Poreacutem pouco desse poderde processamento provido pelos muacuteltiplos nuacutecleos eacute aproveitado de maneira efetiva pelas apli-caccedilotildees devido agrave dificuldade de se escrever sistemas concorrentes Com o objetivo de tornar odesenvolvimento desse tipo de sistema mais palpaacutevel alguns novos mecanismos de sincroniza-ccedilatildeo e paralelismo vem sendo propostos em linguagens de programaccedilatildeo funcional Esse tipode linguagem prega um estilo de programaccedilatildeo baseado em funccedilotildees puras e dados imutaacuteveisque facilita o desenvolvimento de programas concorrentes Com o objetivo de entender mel-hor esses benefiacutecios este trabalho faz um estudo comparativo entre as linguagens funcionaisClojure e Haskell com foco na utilizaccedilatildeo de Memoacuteria Transacional em Software Para isso foiutilizado como objeto de estudo a implementaccedilatildeo de um motor de busca paralelo em ambas aslinguagens

Palavras-chave memoacuteria transacional em sotware programaccedilatildeo funcional concorrecircnciamotor de busca recuperaccedilatildeo de informaccedilatildeo

v

Abstract

The multicore processors are present in almost every modern computer including mobile de-vices like smartphones and tablets However only a small portion of the processing powerprovided by the multiple cores are actually used by the applications due the difficulty of writ-ing concurrent systems Aiming to make the development of this kind of system become moretangible some new synchronization mecanisms are being proposed in functional programminglanguages Functional programming emphasizes a programming style based on pure functionsand immutable data which makes the development of concurrent programs easier The aim ofthis project is to make a comparative study of the functional languages Clojure and Haskellfocusing on the use of Sotfware Transacional Memory To this end the implementation of aparallel search engine was used as object of study

Keywords software transacional memory functional programming concurrency search en-gine information retrieval

vi

Sumaacuterio

1 Introduccedilatildeo 1

2 Memoacuteria Transacional em Software 321 Transaccedilotildees 322 Bloco atocircmico 423 Composabilidade 424 Controle de Concorrecircncia 625 Estrateacutegias de Atualizaccedilatildeo e Versionamento 626 Outros Aspectos 7

3 Clojure 831 Conceitos baacutesicos 832 Interoperabilidade com Java 933 Threads 934 Memoacuteria Transacional em Clojure 10

341 Controle de Concorrecircncia 11342 Variaacuteveis transacionais 11343 Blocos atocircmicos 11344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 12

4 Haskell 1341 Conceitos baacutesicos 1342 Monads 1443 Threads 1544 Memoacuteria Transacional em Haskell 15

441 Variaacuteveis transacionais 15442 Bloco atocircmico 16443 Bloqueio e escolha 16444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 17

5 Um Motor de Busca Paralelo com STM 1851 Especificaccedilatildeo 1852 Versatildeo sequencial 19

521 Iacutendice 19522 Arquitetura 20

53 Versatildeo paralela 21

vii

SUMAacuteRIO viii

531 Iacutendice 21532 Arquitetura 22

6 Resultados 2461 Comparaccedilatildeo das implementaccedilotildees 2462 Experimentos 26

7 Conclusatildeo 29

Lista de Figuras

51 Representaccedilatildeo do iacutendice 2052 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial 2053 Representaccedilatildeo das duas abordagens de particionamento de iacutendice 2254 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads 23

61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell 2662 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas 2763 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo 2864 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo 28

ix

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 5: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

Resumo

Os processadores multicore estatildeo presentes em praticamente todos os computadores modernosinclusive em dispositivos moacuteveis como telefones celular e tablets Poreacutem pouco desse poderde processamento provido pelos muacuteltiplos nuacutecleos eacute aproveitado de maneira efetiva pelas apli-caccedilotildees devido agrave dificuldade de se escrever sistemas concorrentes Com o objetivo de tornar odesenvolvimento desse tipo de sistema mais palpaacutevel alguns novos mecanismos de sincroniza-ccedilatildeo e paralelismo vem sendo propostos em linguagens de programaccedilatildeo funcional Esse tipode linguagem prega um estilo de programaccedilatildeo baseado em funccedilotildees puras e dados imutaacuteveisque facilita o desenvolvimento de programas concorrentes Com o objetivo de entender mel-hor esses benefiacutecios este trabalho faz um estudo comparativo entre as linguagens funcionaisClojure e Haskell com foco na utilizaccedilatildeo de Memoacuteria Transacional em Software Para isso foiutilizado como objeto de estudo a implementaccedilatildeo de um motor de busca paralelo em ambas aslinguagens

Palavras-chave memoacuteria transacional em sotware programaccedilatildeo funcional concorrecircnciamotor de busca recuperaccedilatildeo de informaccedilatildeo

v

Abstract

The multicore processors are present in almost every modern computer including mobile de-vices like smartphones and tablets However only a small portion of the processing powerprovided by the multiple cores are actually used by the applications due the difficulty of writ-ing concurrent systems Aiming to make the development of this kind of system become moretangible some new synchronization mecanisms are being proposed in functional programminglanguages Functional programming emphasizes a programming style based on pure functionsand immutable data which makes the development of concurrent programs easier The aim ofthis project is to make a comparative study of the functional languages Clojure and Haskellfocusing on the use of Sotfware Transacional Memory To this end the implementation of aparallel search engine was used as object of study

Keywords software transacional memory functional programming concurrency search en-gine information retrieval

vi

Sumaacuterio

1 Introduccedilatildeo 1

2 Memoacuteria Transacional em Software 321 Transaccedilotildees 322 Bloco atocircmico 423 Composabilidade 424 Controle de Concorrecircncia 625 Estrateacutegias de Atualizaccedilatildeo e Versionamento 626 Outros Aspectos 7

3 Clojure 831 Conceitos baacutesicos 832 Interoperabilidade com Java 933 Threads 934 Memoacuteria Transacional em Clojure 10

341 Controle de Concorrecircncia 11342 Variaacuteveis transacionais 11343 Blocos atocircmicos 11344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 12

4 Haskell 1341 Conceitos baacutesicos 1342 Monads 1443 Threads 1544 Memoacuteria Transacional em Haskell 15

441 Variaacuteveis transacionais 15442 Bloco atocircmico 16443 Bloqueio e escolha 16444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 17

5 Um Motor de Busca Paralelo com STM 1851 Especificaccedilatildeo 1852 Versatildeo sequencial 19

521 Iacutendice 19522 Arquitetura 20

53 Versatildeo paralela 21

vii

SUMAacuteRIO viii

531 Iacutendice 21532 Arquitetura 22

6 Resultados 2461 Comparaccedilatildeo das implementaccedilotildees 2462 Experimentos 26

7 Conclusatildeo 29

Lista de Figuras

51 Representaccedilatildeo do iacutendice 2052 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial 2053 Representaccedilatildeo das duas abordagens de particionamento de iacutendice 2254 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads 23

61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell 2662 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas 2763 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo 2864 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo 28

ix

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 6: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

Abstract

The multicore processors are present in almost every modern computer including mobile de-vices like smartphones and tablets However only a small portion of the processing powerprovided by the multiple cores are actually used by the applications due the difficulty of writ-ing concurrent systems Aiming to make the development of this kind of system become moretangible some new synchronization mecanisms are being proposed in functional programminglanguages Functional programming emphasizes a programming style based on pure functionsand immutable data which makes the development of concurrent programs easier The aim ofthis project is to make a comparative study of the functional languages Clojure and Haskellfocusing on the use of Sotfware Transacional Memory To this end the implementation of aparallel search engine was used as object of study

Keywords software transacional memory functional programming concurrency search en-gine information retrieval

vi

Sumaacuterio

1 Introduccedilatildeo 1

2 Memoacuteria Transacional em Software 321 Transaccedilotildees 322 Bloco atocircmico 423 Composabilidade 424 Controle de Concorrecircncia 625 Estrateacutegias de Atualizaccedilatildeo e Versionamento 626 Outros Aspectos 7

3 Clojure 831 Conceitos baacutesicos 832 Interoperabilidade com Java 933 Threads 934 Memoacuteria Transacional em Clojure 10

341 Controle de Concorrecircncia 11342 Variaacuteveis transacionais 11343 Blocos atocircmicos 11344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 12

4 Haskell 1341 Conceitos baacutesicos 1342 Monads 1443 Threads 1544 Memoacuteria Transacional em Haskell 15

441 Variaacuteveis transacionais 15442 Bloco atocircmico 16443 Bloqueio e escolha 16444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 17

5 Um Motor de Busca Paralelo com STM 1851 Especificaccedilatildeo 1852 Versatildeo sequencial 19

521 Iacutendice 19522 Arquitetura 20

53 Versatildeo paralela 21

vii

SUMAacuteRIO viii

531 Iacutendice 21532 Arquitetura 22

6 Resultados 2461 Comparaccedilatildeo das implementaccedilotildees 2462 Experimentos 26

7 Conclusatildeo 29

Lista de Figuras

51 Representaccedilatildeo do iacutendice 2052 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial 2053 Representaccedilatildeo das duas abordagens de particionamento de iacutendice 2254 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads 23

61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell 2662 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas 2763 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo 2864 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo 28

ix

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 7: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

Sumaacuterio

1 Introduccedilatildeo 1

2 Memoacuteria Transacional em Software 321 Transaccedilotildees 322 Bloco atocircmico 423 Composabilidade 424 Controle de Concorrecircncia 625 Estrateacutegias de Atualizaccedilatildeo e Versionamento 626 Outros Aspectos 7

3 Clojure 831 Conceitos baacutesicos 832 Interoperabilidade com Java 933 Threads 934 Memoacuteria Transacional em Clojure 10

341 Controle de Concorrecircncia 11342 Variaacuteveis transacionais 11343 Blocos atocircmicos 11344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 12

4 Haskell 1341 Conceitos baacutesicos 1342 Monads 1443 Threads 1544 Memoacuteria Transacional em Haskell 15

441 Variaacuteveis transacionais 15442 Bloco atocircmico 16443 Bloqueio e escolha 16444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria 17

5 Um Motor de Busca Paralelo com STM 1851 Especificaccedilatildeo 1852 Versatildeo sequencial 19

521 Iacutendice 19522 Arquitetura 20

53 Versatildeo paralela 21

vii

SUMAacuteRIO viii

531 Iacutendice 21532 Arquitetura 22

6 Resultados 2461 Comparaccedilatildeo das implementaccedilotildees 2462 Experimentos 26

7 Conclusatildeo 29

Lista de Figuras

51 Representaccedilatildeo do iacutendice 2052 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial 2053 Representaccedilatildeo das duas abordagens de particionamento de iacutendice 2254 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads 23

61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell 2662 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas 2763 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo 2864 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo 28

ix

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 8: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

SUMAacuteRIO viii

531 Iacutendice 21532 Arquitetura 22

6 Resultados 2461 Comparaccedilatildeo das implementaccedilotildees 2462 Experimentos 26

7 Conclusatildeo 29

Lista de Figuras

51 Representaccedilatildeo do iacutendice 2052 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial 2053 Representaccedilatildeo das duas abordagens de particionamento de iacutendice 2254 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads 23

61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell 2662 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas 2763 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo 2864 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo 28

ix

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 9: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

Lista de Figuras

51 Representaccedilatildeo do iacutendice 2052 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial 2053 Representaccedilatildeo das duas abordagens de particionamento de iacutendice 2254 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads 23

61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell 2662 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas 2763 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo 2864 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo 28

ix

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 10: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

CAPIacuteTULO 1

Introduccedilatildeo

Escrever programas que tirem bom proveito das arquiteturas multicore eacute um desfio para En-genharia de Software Programas paralelos executam de maneira natildeo-determiniacutestica por issosatildeo difiacuteceis de testar e bugs podem ser quase impossiacuteveis de ser reproduzidos Aliado a issoas abstraccedilotildees baseadas em locks que constituem a tecnologia dominante para programaccedilatildeoconcorrente satildeo conceitualmente inapropriadas para lidar com esse paradigma [1]

Um dos problemas que torna programaccedilatildeo baseada em locks impraticaacutevel eacute a impossibil-idade de se escrever coacutedigos reusaacuteveis Por exemplo considere o cenaacuterio em que um desen-volver escreveu uma biblioteca que conteacutem a implementaccedilatildeo de uma tabela hash A API dessatabela fornece meacutetodos para inserccedilatildeo e remoccedilatildeo de elementos que satildeo thread-safe Agorasuponha que outro desenvolvedor esteja utilizando essa mesma biblioteca em seu softwareonde ele utiliza duas instacircncias da tabela t1 e t2 Na regra de negoacutecio de seu software osegundo desenvolvedor precisa remover um item A de t1 e inseriacute-lo em t2 sem que o estadointermediaacuterio em que nenhuma das tabelas conteacutem A esteja visiacutevel para outras threads Auacutenica forma de atender agrave esse requisito seria se o primeiro desenvolvedor previsse esse caso deuso e fornecesse na API da biblioteca meacutetodos que permitissem realizar lock e unlock da tabelaAleacutem de ser algo improvaacutevel de ser previsto esse tipo de meacutetodo quebra a abstraccedilatildeo de tabelahash e induz problemas de sincronizaccedilatildeo como deadlock e condiccedilatildeo de corrida caso o usuaacuterioda biblioteca realize operaccedilotildees de lock e unlock na ordem errada ou simplismente esqueccedila derealizaacute-las Em resumo operaccedilotildees indiviualmente corretas natildeo podem ser compostas em umaoperaccedilatildeo maior que tambeacutem seja correta [2]

Diante desse cenaacuterio muitos vecircem a popularizaccedilatildeo de linguagens funcionais como algoiminente [3] Um do fatores que confirma essa tendecircncia eacute o recente surgimento de novaslinguagens funcionais como Clojure F e Scala Outras linguagens funcionais como Erlange Haskell apesar de natildeo terem sido criadas recentemente tambeacutem tecircm ganho muitos adeptosnos uacuteltimos anos [4]

O principal motivo por esse interesse em torno das linguagens funcionais satildeo algumas car-acteriacutesticas inerentes ao estilo de programaccedilatildeo funcional que satildeo bastante convenientes para odesenvolvimento de sistemas concorrentes como por exemplo a ecircnfase em funccedilotildees puras e aausecircncia de estado e dados mutaacuteveis Aleacutem disso boa parte dessas linguagens tentam de al-guma forma tornar programaccedilatildeo concorrente algo mais palpaacutevel Um bom exemplo disso eacute queboa parte delas tecircm boas implementaccedilotildees de mecanismos alternativos agrave sincronizaccedilatildeo baseadaem locks como o Modelo de Atores [5] e Memoacuteria Transacional em Software [6]

O foco deste trabalho eacute no mecanismo de Memoacuteria Transacional em Software especial-mente nas implementaccedilotildees presentes nas linguagens Clojure e Haskell Um dos problemasque esse mecanismo tenta resolver que foi descrito anteriormente eacute possibilitar ao progra-

1

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 11: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

CAPIacuteTULO 1 INTRODUCcedilAtildeO 2

mador compor operaccedilotildees sobre uma memoacuteria compartilhada de forma que elas sejam execu-tadas atomicamente e em isolamento Com o intuito de entender melhor esse mecanismo napraacutetica bem como levantar as diferenccedilas entre as implementaccedilotildees de Clojure e Haskell essetrabalho utilizou um programa natildeo trivial que jaacute foi utilizado em trabalhos relacionados comoobjeto de estudo Esse programa consiste em um motor de busca paralelo que foi implementadotanto em Clojure quanto em Haskell utilizando memoacuteria transacional

Este trabalho estaacute dividido da seguinte forma no Capiacutetulo 2 seraacute apresentado o conceito dememoacuteria transacional e quais requisitos que devem ser atentidos em uma implementaccedilatildeo dessetipo de mecanismo Nos Capiacutetulos 3 e 4 seraacute feita uma breve apresentaccedilatildeo sobre as lingua-gens Clojure e Haskell respectivamente bem como o levantamento de detalhes dos modelosde memoacuteria transacional implementados em cada linguagem O Capiacutetulo 5 focaraacute no detal-hamento do problema que foi implementado e na descriccedilatildeo de como foi projetada e implemen-tada a soluccedilatildeo proposta No Capiacutetulo 6 seratildeo discutidos os resultados comparativos obtidosnesse projeto Por fim o Capiacutetulo 7 conteacutem as consideraccedilotildees finais desse trabalho e algumassugestotildees de melhorias que podem ser exploradas em trabalhos futuros

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 12: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

CAPIacuteTULO 2

Memoacuteria Transacional em Software

Memoacuteria Transacional em Software (Software Transactional Memory ou STM) foi propostoprimeiramente por Shavit e Touitou em 1995 [6] Antes disso o conceito de memoacuteria transa-cional jaacute havia sido proposto em 1977 por Lomet [7] onde foi descrito como se poderia utilizaras propriedades das transaccedilotildees de banco de dados para coordenar leituras e escritas concor-rentes em dados compartilhados na memoacuteria

Em poucas palavras pode-se definir STM como um mecanismo que permite a execuccedilatildeo degrupos de operaccedilotildees sobre a memoacuteria de maneira atocircmica [2] Dessa forma ao inveacutes de pro-teger os dados compartilhados utilizando locks pode-se explicitamente agrupar as operaccedilotildeesque devem acontecer ao mesmo tempo Isso elimina muitos dos problemas da sincroniza-ccedilatildeo baseada em locks como inversatildeo de prioridade deadlocks e a tensatildeo entre concorrecircncia egranularidade [2]

No decorrer deste capiacutetulo seratildeo vistos alguns conceitos importantes que fazem parte de umsistema de memoacuteria transacional como transaccedilotildees blocos atocircmicos e composiccedilatildeo Tambeacutemseratildeo abordados algumas das estrateacutegias que podem ser utilizadas para realizar controle deconcorrecircncia e versionamento

21 Transaccedilotildees

Transaccedilatildeo eacute um conceito oriundo da aacuterea de banco de dados e que vem sendo utilizado comsucesso haacute bastante tempo Um sistema de gerenciamento de banco de dados (SGBD) eacute umsistema extremamente concorrente onde vaacuterios usuaacuterios podem acessar inserir remover e atu-alizar dados armazenados no banco de dados ao mesmo tempo As transaccedilotildees satildeo os mecanis-mos providos ao usuaacuterio de um SGBD para que este realize uma sequecircncia de operaccedilotildees semprecisar se preocupar com o aspecto concorrente do sistema

Elmasri e Navathe [8] definem uma transaccedilatildeo como um programa em execuccedilatildeo ou pro-cesso que inclui um ou mais acessos ao banco de dados tais como leitura ou atualizaccedilatildeodos registros do banco de dados Uma definiccedilatildeo mais interessante eacute dada por Harris et al[9] que define uma transaccedilatildeo como uma sequecircncia de accedilotildees que parecem indivisiacuteveis e in-stantacircneas para um observador externo A partir dessas definiccedilotildees derivam-se quatro pro-priedades conhecidas como ACID que satildeo utilizadas para descrever o comportamento dese-jado das transaccedilotildees [10]

bull Atomicidade - Ou todas as accedilotildees de uma transaccedilatildeo acontecem e satildeo efetivadas (commitem inglecircs) ou em caso de falha qualquer accedilatildeo executada por uma transaccedilatildeo parcial seraacutedesfeita e a transaccedilatildeo seraacute abortada

3

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 13: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

22 BLOCO ATOcircMICO 4

bull Consistecircncia - A transaccedilatildeo executada em isolamento preserva a consistecircncia do banco dedados Por exemplo se uma quantia de dinheiro seraacute transferido de uma conta A parauma conta B eacute necessaacuterio que a soma do saldo de A e B se mantenha inalterada

bull Isolamento - Uma transaccedilatildeo natildeo deve ter acesso agrave nenhum estado intermediaacuterio incon-sistente criado por outra transaccedilatildeo que estaacute sendo executada concorrentemente

bull Durabilidade - Se uma transaccedilatildeo foi efetivada as mudanccedilas feitas no banco de dadosdevem sobreviver agrave qualquer falha de sistema

As transaccedilotildees de um sistema de memoacuteria transacional satildeo natildeo-duraveis A durabilidadenatildeo eacute necessaacuteria uma vez que os dados manipulados por uma transaccedilatildeo natildeo satildeo mantidos apoacuteso termino da execuccedilatildeo do programa [9]

22 Bloco atocircmico

Um dos conceitos chave de STM eacute o bloco atocircmico pois eacute ele que delimita uma transaccedilatildeo Obloco atocircmico eacute uma construccedilatildeo que permite ao programador agrupar operaccedilotildees para seremexecutadas em isolamento Normalmente essa ideia eacute representada nas linguagens de progra-maccedilao pela palavra-chave atomic seguida de um bloco de escopo que conteacutem o coacutedigo datransaccedilatildeo Os blocos atocircmicos tambeacutem podem ser representado por meio da definiccedilatildeo de meacuteto-dos ou funccedilotildees atocircmicas [9]

atomic if (x = null)

xbar()

y = true

void atomic foo() if (x = null)

xbar()

y = true

Coacutedigo 1 Exemplos de blocos atocircmicos

Existem dois resultados definidos distintos que podem decorrer da execuccedilatildeo do coacutedigo deum bloco atocircmico (uma transaccedilatildeo) Ou toda a transaccedilatildeo eacute concluiacuteda com sucesso e as mudanccedilasficam disponiacuteveis para o restante do programa ou a transaccedilatildeo aborta e manteacutem o estado doprograma inalterado Um terceiro resultado indefinido pode ocorrer quando a transaccedilatildeo natildeotermina [11]

Uma transaccedilatildeo eacute abortada quando durante sua execuccedilatildeo o valor de uma variaacutevel com-partilhada eacute alterado antes que essa transaccedilatildeo realize commit Quando isso ocorre o sistemare-executa a transaccedilatildeo na expectativa que natildeo ocorra conflito novamente

23 Composabilidade

Uma das grandes vantagens dos blocos atocircmicos eacute a possibilidade de se combinar uma seacuterie deoperaccedilotildees atocircmicas individuais de forma que o resultado continue sendo atocircmico Essa possi-

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 14: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

23 COMPOSABILIDADE 5

bilidade de compor trechos de coacutedigo ou funccedilotildees thread-safe mantendo o resultado thread-safenatildeo eacute algo faacutecil de se alcanccedilar quando se utiliza abstraccedilotildees baseadas em locks Para exempli-ficar essa propriedade o Coacutedigo 2 mostra uma possiacutevel implementaccedilatildeo1 de um problema claacutes-sico utilizado para demonstrar a utilizaccedilatildeo de memoacuteria trasacional Se trata do problema dastransaccedilotildees bancaacuterias que consiste na implementaccedilatildeo de um sistema bancaacuterio que seja possiacutevelefetuar transaccedilotildees bancaacuterias concorrentemente Para garantir a consistecircncia do saldo das contaseacute necessaacuterio que durante a transferecircncia de um conta para outra o resultado intermediaacuterio emque a quantia desejada foi sacada de uma conta e ainda natildeo foi depositada na outra natildeo sejavisiacutevel para outras threads

class Account private transactional int balance

public withdraw(int aumont) thisbalance -= aumont

public deposit(int aumont) thisbalance += aumont

public static transfer(Account from Account to int aumont) atomic

fromwithdraw(aumont)todeposit(aumont)

Coacutedigo 2 Exemplo da transferecircncia bancaacuteria com STM

Com uma implementaccedilatildeo desse tipo eacute garantido que ao final da execuccedilatildeo do meacutetodo trans-fer a quantia desejada foi transferida de uma conta para outra sem que nenhuma outra threadtivesse acesso ao estado intermediaacuterio

Aleacutem do benefiacutecio de se poder compor operaccedilotildees para serem executadas de maneira atocircmi-cas como pode-se perceber o coacutedigo final eacute consideravelmente mais simples do que a soluccedilatildeobaseada em locks uma vez que os mecanismos de sincronizaccedilatildeo satildeo impliacutecitos Outra van-tagem eacute que o coacutedigo da soluccedilatildeo que utiliza memoacuteria transacional eacute bem semelhante agrave soluccedilatildeosequencial do problema

1Esse coacutedigo eacute apenas figurativo Mostra como o problema poderia ser modelado em uma linguagem orientadaagrave objetos semelhante agrave Java com suporte agrave STM

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 15: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

24 CONTROLE DE CONCORREcircNCIA 6

24 Controle de Concorrecircncia

O fato de que uma transaccedilatildeo deve ser abortada e re-executada quando acontece um conflitonatildeo impotildee nenhuma restriccedilatildeo com relaccedilatildeo a como e quando o sistema de memoacuteria transacionaldeve detectar o conflito e acionar o abortamento Esse tipo de decisatildeo eacute feito pelo controle deconcorrecircncia

Para que os conflitos sejam detectados e resolvidos corretamente um sistema de memoacuteriatransacional precisa de sincronizaccedilatildeo para coordenar o acesso concorrente ao dados compar-tilhados Essa sincronizaccedilatildeo eacute realizada com base em trecircs eventos que podem ocorrer emdiferentes momentos mas natildeo em uma ordem diferente [9]

bull Ocorrecircncia - Acontece quando duas transaccedilotildeees realizam operaccedilotildees escrita-escrita ouescrita-leitura no mesmo dado compartilhado

bull Detecccedilatildeo - Acontece quando o sistema de memoacuteria transacional determina que o conflitoocorreu

bull Resoluccedilatildeo - Acontece quando alguma accedilatildeo eacute tomada para evitar o conflito

Existem duas abordagens claacutessicas para lidar com o controle de concorrecircncia a abordagempessimista e a otimista Na abordagem pessimista os eventos de detecccedilatildeo e resoluccedilatildeo aconte-cem instantaneamente apoacutes o evento de ocorrecircncia Jaacute na abordagem otimista esses eventosocorrem ao final da transaccedilatildeo antes do commit

Agrave primeira vista a abordagem pessimista parece mais eficiente jaacute que evita a execuccedilatildeode operaccedilotildees desnecessaacuterias apoacutes a detecccedilatildeo do conflito Poreacutem esse benefiacutecio natildeo eacute tatildeo claroquando se considera um cenaacuterio onde mais de duas transaccedilotildees concorrentes entram em conflitoPor exemplo assuma que duas transaccedilotildees A e B entram em conflito com uma transaccedilatildeo C masnatildeo conflitam entre si Seria suficiente abortar C entretanto essa detecccedilatildeo natildeo eacute trivial dado quea transaccedilatildeo C natildeo tem conhecimento preacutevio sobre conflitos entre A e B [12] Outro problemaque envolve a abordagem pessimista eacute a possibilidade de deadlocks jaacute que para garantir quea detecccedilatildeo aconteccedila logo apoacutes a ocorrecircncia eacute necessaacuterio que a implementaccedilatildeo do controle deconcorrecircncia utilize locks [9]

De uma forma geral se os conflitos satildeo frequentes o controle de concorrecircncia pessimistatende a ser mais efetivo Jaacute no caso onde os conflitos satildeo raros o controle de concorrecircnciaotimista eacute geralmente mais raacutepido devido ao menor overhead com aquisiccedilatildeo de locks e poraumentar a concorrecircncia entre as transaccedilotildees

25 Estrateacutegias de Atualizaccedilatildeo e Versionamento

Um dos problemas que precisam ser levados em consideraccedilatildeo quando se estaacute projetando umsistema de memoacuteria transacional eacute como a transaccedilatildeo deve atualizar a memoacuteria compartilhadaAssim como no controle de concorrecircncia existem duas estrateacutegias possiacuteveis atualizaccedilotildees di-retas (direct updates) e atualizaccedilotildees tardias (deferred updates) [13]

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 16: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

26 OUTROS ASPECTOS 7

Em um sistema que realiza atualizaccedilotildees diretas o versionamento das escritas na memoacuteria eacutefeito seguindo o conceito de eager versioning Nesse tipo de sistema a transaccedilatildeo modifica di-retamente na memoacuteria o dado compartilhado e manteacutem um undo-log com os valores que foramsobrescritos Posteriormente se a transaccedilatildeo for abortada esse log eacute utilizado para restauraro conteuacutedo inicial da memoacuteria Esse tipo de estrateacutegia implica na utilizaccedilatildeo de um controlede concorrecircncia pessimista jaacute que como os valores satildeo escritos diretamente na memoacuteria eacutenecessaacuterio garantir a exclusatildeo muacutetua durante a atualizaccedilatildeo

Por outro lado em um sistema com atualizaccedilotildees tardias utiliza-se o conceito de lazy ver-sioning Nesta estrateacutegia os valores que seratildeo escritos na memoacuteria satildeo armazenados em umredo-log (privado para cada transaccedilatildeo) e as leituras verificam primeiramente esse log paragarantir o isolamento da transaccedilatildeo Apoacutes o teacutermino da transaccedilatildeo se natildeo tiver havido conflitosa transaccedilatildeo atualiza a memoacuteria compartilhada a partir dos valores do log em caso contraacuterio atransaccedilatildeo eacute abortada e o log eacute descartado

A estrateacutegia de atualizaccedilatildeo tardia eacute eficaz quando muitas transaccedilotildees abortam Como oestado compartilhado se manteacutem inalterado natildeo eacute necessaacuterios realizar restauraccedilatildeo Poreacutemnesse tipo de estrateacutegia eacute mais trabalhoso efetivar a transaccedilatildeo jaacute que a accedilatildeo de commit deveparacer atocircmica para as demais transaccedilotildees

26 Outros Aspectos

Como foi visto durante esse capiacutetulo as escolhas das estrateacutegias que seratildeo utilizadas em umdeterminado sistema de memoacuteria transacional tem impacto direto em sua performance em difer-entes cenaacuterios Aleacutem do que foi dito existem outros pontos que devem ser levados em consid-eraccedilatildeo quando se deseja projetar um sistema de STM Por exemplo existe a possibilidade dosistema entrar em livelock quando algumas transaccedilotildees natildeo conseguem realizar commit devidoagrave conflitos repetidos Para lidar com esse tipo de problema os sistemas de STM normalmenteimplementam algum mecanismo de gerenciamento de contenccedilatildeo [12]

Outro problema que precisa de atenccedilatildeo eacute como lidar com efeitos colaterais Para que umatransaccedilatildeo consiga ser abortada de maneira correta eacute necessaacuterio que dentro de uma transaccedilatildeo asoperaccedilotildees realizadas natildeo contenham nenhum efeito senatildeo leitura e escrita da memoacuteria compar-tilhada [1] Operaccedilotildees como escrita em um arquivo ou a impressatildeo de mensagem na tela natildeopodem ser desfeitas e por isso devem ser evitadas

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 17: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

CAPIacuteTULO 3

Clojure

Clojure eacute um dialeto de Lisp criado por Rich Hickey em 2007 A principal motivaccedilatildeo paracriaccedilatildeo da linguagem foi a necessidade do autor em ter uma linguagem que fosse projetadapara concorrecircncia e tivesse como base uma plataforma bem aceita pela induacutestria segura e comboa performance como a maacutequina virtual de Java (JVM) [14]

Neste capiacutetulo inicialmente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos de Clojuree sua interoperabilidade com Java em seguida seraacute abordado a parte de concorrecircncia onde seraacuteexplicado como funcionam threads em Clojure e como se caracteriza o modelo de memoacuteriatransacional da linguagem

31 Conceitos baacutesicos

Clojure eacute uma linguagem funcional dinamicamente tipada que enfatiza funccedilotildees puras e es-truturas de dados imutaacuteveis como as principais formas de se contruir programas Por ser umdialeto de Lisp Clojure tem uma sintaxe simples que eacute bastante conhecida pela notaccedilatildeo prefix-ada e pelos parenteses

O coacutedigo Clojure eacute processado em trecircs fases tempo de leitura tempo de compilaccedilatildeo etempo de execuccedilatildeo Em tempo de leitura o Reader lecirc o coacutedigo fonte e o converte pra umaestrutura de dados tipicamente uma lista de listas Em tempo de compilaccedilatildeo a estrura de dadoseacute convertida para bytecode Java e em tempo de execuccedilatildeo o bytecode Java eacute executado [15]

Cada operaccedilatildeo em Clojure eacute representada por uma funccedilatildeo um macro ou uma forma espe-cial Uma funccedilatildeo eacute executada em tempo de execuccedilatildeo e consiste em uma unidade de computaccedilatildeoque recebe uma entrada e produz uma saiacuteda Um macro eacute uma construccedilatildeo bastante similar agraveuma funccedilatildeo mas que eacute expandido em coacutedigo Clojure em tempo de compilaccedilatildeo Um macropode ser visto como uma maneira programaacutetica de se extender o compilador da linguagem Jaacuteuma forma especial eacute uma operaccedilatildeo reconhecida pelo compilador mas que natildeo eacute implementadaem Clojure

Outra observaccedilatildeo importante eacute que embora Clojure natildeo seja uma linguagem lazy faz forteuso de lazy evaluation em suas estruturas de dados Todas as funccedilotildees que operam sobre se-quecircncias (listas strings streams aacutervores XML etc) retornam sequecircncias lazy

Como exemplo o Coacutedigo 3 conteacutem uma funccedilatildeo em Clojure que computa o somatoacuterio deuma lista de nuacutemeros A linha 1 eacute um comentaacuterio comentaacuterios em Clojure comeccedilam com ocaracter As demais linhas satildeo a funccedilatildeo em si O iniacutecio da funccedilatildeo estaacute na linha 2 onde utiliza-se o macro defn para definir a funccedilatildeo Esse macro recebe um nome que eacute o identificadorda funccedilatildeo (no caso dessa funccedilatildeo sumrsquo) uma lista de argumentos ([numbers]) e corpo da

8

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 18: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

32 INTEROPERABILIDADE COM JAVA 9

1 Computes the sum of a finite list of numbers2 (defn sumrsquo [numbers]3 (loop [ns numbers acc 0]4 (if (empty ns)5 acc6 (recur (rest ns) (+ acc (first ns))))))

Coacutedigo 3 Funccedilatildeo sumrsquo em Clojure

funccedilatildeo (linhas 3 a 6) O algoritmo recursivo do somatoacuterios estaacute expresso e termos da formaespecial loop A escrita dessa forma especial eacute semelhante agrave uma recursatildeo mas eacute traduzidainternamente para um laccedilo para evitar o consumo de muito espaccedilo na pilha Esse tipo demecanismo eacute necessaacuterio pelo fato da JVM natildeo suportar tail call optimization

32 Interoperabilidade com Java

Um dos objetivos de Clojure eacute fazer uso da vasta coleccedilatildeo de bibliotecas escritas em Java Porisso eacute possiacutevel dentro do coacutedigo Clojure usar qualquer classe ou interface Java Para que issoseja possiacutevel Clojure fornece algumas formas especiais para criaccedilatildeo de objetos e chamada demeacutetodos

(defn diff-first-char [string](- (charAt string 0) (CharactergetNumericValue 0)))

Coacutedigo 4 Exemplo de chamada de meacutetodos Java em Clojure

A funccedilatildeo presente no Coacutedigo 4 exemplifica as formas esqueciais para chamada de meacutetodoAs principais satildeo

bull Chamada de um meacutetodo de instacircncia

bull Chamada de um meacutetodo estaacutetico

bull Construccedilatildeo de um objeto

(metodo objeto args)

(Classemetodo args)

(Classe args)

33 Threads

Todas as bibliotecas disponiacuteveis em Java para manipulaccedilatildeo de threads (Thread ThreadPoolExecutor etc) podem ser utilizadas em Clojure Aleacutem dessas a linguagem define algu-mas construccedilotildees que abstraem a criaccedilatildeo e o gerenciamento explicito de threads entre eles ofuture e os agentes

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 19: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 10

O future eacute um macro que executa um corpo com um conjunto de expressotildees em um dosthread pools (CachedThreadPool) gerenciados pela biblioteca de tempo de execuccedilatildeo dalinguagem Ele eacute uacutetil para execuccedilatildeo de tarefas demoradas nos quais natildeo se precisa do resultadoimediatamente O resultado da tarefa eacute acessado deferenciando-se o objeto retornado pelomacro Se a tarefa ainda estiver em execuccedilatildeo no momento da deferenciaccedilatildeo a chamada iraacutebloquear ateacute que a tarefa termine

(println Creating future)(def future-object (future (find-primes-until 999)))(println Future created)(println The result is future-object)

Coacutedigo 5 Exemplo da utilizaccedilatildeo future em Clojure

Um agente representado na linguagem pela funccedilatildeo agent eacute uma variaacutevel que pode terseu estado alterado por uma accedilatildeo que eacute executada assincronamente Uma accedilatildeo eacute uma funccedilatildeoque recebe como paracircmetro o estado atual do agente e outros paracircmetros opcionais e retornao novo estado do agente Apoacutes a execuccedilatildeo de uma accedilatildeo o novo valor armazenado pelo agenteseraacute o valor retornado por essa accedilatildeo Eacute garantido que para um dado agente apenas uma accedilatildeoseraacute executada por vez

(def x (agent 0)) cria um agente com valor inicial 0(defn increment [c n] (+ c n))(send x increment 5) quando a acao for executada x = 5(send x increment 4) quando a acao for executada x = 9

Coacutedigo 6 Exemplo da utilizaccedilatildeo agentes em Clojure

Para enviar accedilotildees para os agentes utiliza-se as funccedilotildees send e send-off Como a accedilatildeo eacuteexecutada de forma assiacutencrona ambas as funccedilotildees retornam imediatamente A funccedilatildeo senddeve ser utilizada para enviar accedilotildees que fazem uso intensivo de CPU enquanto a funccedilatildeosend-off deve ser utilizada para accedilotildees que fazem mais operaccedilotildees de entrada e saiacuteda In-termanente o sistema de agentes tambeacutem eacute gerenciado por threads pools especiacuteficos

34 Memoacuteria Transacional em Clojure

Memoacuteria transacional eacute um dos principais modelos de concorrecircncia providos por Clojure Essemodelo de STM faz uso de uma abordagem de controle de concorrecircncia otimista com atual-izaccedilotildees tardias Poreacutem como veremos na seccedilatildeo 341 ao inveacutes de utilizar um redo-log comodescrito na seccedilatildeo 25 Clojure utilizada um conceito chamado de snapshot isolation

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 20: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 11

341 Controle de Concorrecircncia

Diferente de outras implementaccedilotildees de memoacuteria transacional o modelo de Clojure utiliza umcontrole de concorrecircncia multi-versatildeo (MVCC) com snapshot isolation [16]

O MVCC manteacutem multiplas versotildees dos dados referenciados em uma transaccedilatildeo Dessaforma cada transaccedilatildeo soacute tem acesso a um snapshot dos dados referenciados por ela que eacutecriado no iniacutecio dessa transaccedilatildeo [17] Quando uma transaccedilatildeo termina ela soacute realizaraacute commitse os valores atualizados pela transaccedilatildeo natildeo tiverem sido mudados por outras transaccedilotildees

Existe um problema relacionado agrave utilizaccedilatildeo de snapshot isolation que se refere agrave restriccedilotildeessobre um conjunto de dados Imagine que uma mesma pessoa pode ter duas contas em ummesmo banco poreacutem apenas uma dessas contas pode estar com saldo devedor Considere duascontas T1 e T2 pertencentes a uma mesma pessoa onde ambas tem saldo 100 reais Se ousuaacuterio executar duas transaccedilotildees concorrentes onde cada uma saca 200 reais de uma das contaseacute possiacutevel que T1 e T2 fiquem com saldo negativo pois ao final das duas transaccedilotildees a restriccedilatildeose manteraacute vaacutelida uma vez que no snapshot de cada transaccedilatildeo uma conta teraacute saldo negativo ea outra positivo [18] Essa anomalia eacute chamada de write skew

Uma das formas de se evitar esse problema eacute utilizando a funccedilatildeo ensure1 Essa funccedilatildeobasicamente impede que outras transaccedilotildees atualizem o valor de uma variaacutevel transacional antesque a transaccedilatildeo atual termine

342 Variaacuteveis transacionais

As variaacuteveis transacionais em Clojure satildeo conhecidas como refs e soacute podem ser alteradasdentro de uma transaccedilatildeo As principais funccedilotildees que permitem alterar o valor de uma var-iaacutevel transacional satildeo ref-set e alter Jaacute a leitura eacute feita deferenciando-se o ref(nome-do-ref) e pode ser realizado em qualquer lugar do programa natildeo somente den-tro de transaccedilotildees

Das funccedilotildees que alteram o valor de um ref a mais simples eacute ref-set Ela recebe umref e um valor como paracircmetros e atualiza o estado da variaacutevel para o valor que foi passadoJaacute a funccedilatildeo alter funciona de forma semelhante ao send e o send-off dos agentes Elarecebe como paracircmetro o ref e uma funccedilatildeo que seraacute utilizada para atualizar o valor do refbaseado no estado atual e apoacutes executar retorna o valor atual da variaacutevel

343 Blocos atocircmicos

O bloco atocircmico de Clojure eacute representado pelo macro dosync Ele recebe como paracircmetroum conjunto de expressotildees as executa em isolamento e retorna o valor da uacuteltima expressatildeo

Uma caracteriacutestica importante das transaccedilotildees de Clojure eacute que embora seja explicatamenterecomendado evitar a utilizaccedilatildeo de operaccedilotildees que causam efeito colateral dentro de uma transaccedilatildeoisso pode ser feito A maneira de evitar que isso aconteccedila eacute atraveacutes do macro io2

1httpclojuregithubioclojureclojurecore-apihtmlclojurecoreensure2httpclojuregithubioclojureclojurecore-apihtmlclojurecoreio

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 21: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

34 MEMOacuteRIA TRANSACIONAL EM CLOJURE 12

344 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 7 eacute uma possiacutevel implementaccedilatildeo em Clojure para o problema da transaccedilatildeo bancaacuteriadescrito anteriormente Nas linhas 1 e 2 constam a declaraccedilatildeo dos refs que satildeo utilizadospara representar as contas A funccedilatildeo de saque e depoacutesito estatildeo definidas nas linhas 4 e 7respectivamente Ambas adicionam ou removem uma quantia da conta utilizando a funccedilatildeoalter Na linha 10 estaacute definida a funccedilatildeo que realiza a transferecircncia entre as contas Utiliza-se o macro dosync para delimitar a transaccedilatildeo Eacute importante notar que as funccedilotildees withdrawe deposit soacute podem ser chamadas dentro de uma transaccedilatildeo Caso elas sejam chamadas forade um bloco dosync aconteceraacute um erro em tempo de execuccedilatildeo A linha 15 exemplifica autilizaccedilatildeo da funccedilatildeo transfer onde estaacute sendo transferido o valor 10 entre as duas contasem questatildeo

1 (def account1 (ref 100))2 (def account2 (ref 0))3

4 (defn withdraw [account amount]5 (alter account - amount))6

7 (defn deposit [account amount]8 (alter account + amount))9

10 (defn transfer [from to amount]11 (dosync12 (withdraw from amount)13 (deposit to amount)))14

15 (transfer account1 account2 10)

Coacutedigo 7 Transaccedilatildeo bancaacuteria em Clojure

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 22: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

CAPIacuteTULO 4

Haskell

Haskell eacute uma linguagem com fortes raiacutezes acadecircmica Em meados de 1980 haviam diversaslinguagens funcionais lazy que eram utilizadas como objeto de estudo por diferentes gruposde pesquisa Haskell foi criada por volta de 1990 pelo esforccedilo dos pesquisadores da aacuterea emunificar as pesquisas nesse tipo de paradigma em uma linguagem comum [19]

Neste capiacutetulo primeiramente seraacute feita uma breve introduccedilatildeo ao conceitos baacutesicos deHaskell e Monads em seguida seraacute abordado a parte de concorrecircncia onde seraacute explicadocomo funcionam as threads em Haskell e como se caracteriza o modelo de memoacuteria transa-cional da linguagem

41 Conceitos baacutesicos

Para entender como Haskell funciona eacute preciso ter em mente algumas das caracteriacutesticas quea tornam bem distinta das demais linguagens de programaccedilatildeo Primeiramente Haskell eacute umalinguagem puramente funcional isso quer dizer que a principal unidade de computaccedilatildeo dalinguagem satildeo funccedilotildees puras Uma funccedilatildeo pura eacute uma funccedilatildeo que ao ser aplicada agrave uma dadaentrada retorna um valor e eacute garantido que nenhuma operaccedilatildeo realizada por essa funccedilatildeo temefeito colateral

Haskell tambeacutem eacute uma linguagem lazy Isso quer dizer que a linguagem utiliza uma es-trateacutegia que atrasa a avaliaccedilatildeo de uma expressatildeo ateacute que seu valor seja realmente necessaacuterio Alinguagem tambeacutem tem um sistema de tipos estaacutetico com inferecircncia de tipos Essa caracteriacutes-tica faz com que seja possiacutevel que o programador omita as anotaccedilotildees de tipos das funccedilotildees eexpressotildees sem que o compilador deixe de realizar a validaccedilatildeo de tipos do programa em tempode compilaccedilatildeo

1 -- Computes the sum of a finite list of numbers2 sumrsquo (Num a) =gt [a] -gt a3 sumrsquo [] = 04 sumrsquo (xxs) = x + sumrsquo xs

Coacutedigo 8 Funccedilatildeo sumrsquo em Haskell

O Coacutedigo 8 estaacute mostrando um exemplo de uma funccedilatildeo escrita em Haskell que computa osomatoacuterio de uma lista finita de nuacutemeros A linha 1 eacute apenas um comentaacuterio comentaacuterios deuma linha sempre comeccedilam com -- A linha 2 conteacutem a assinatura do tipo da funccedilatildeo Emboranatildeo seja necessaacuterio eacute recomendado se escrever a assinatura de tipos nas funccedilotildees com o intuito

13

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 23: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

42 MONADS 14

de documentar o coacutedigo [20] O tipo dessa funccedilatildeo indica que a entrada da funccedilatildeo eacute um listade nuacutemeros e a saiacuteda eacute um nuacutemero As linhas 3 e 4 conteacutem o corpo da funccedilatildeo e faz uso decasamento de padrotildees para separar o caso base (linha 3) do caso recursivo (linha 4)

42 Monads

Apesar do fato que qualquer coisa computaacutevel pode ser representada por meio funccedilotildees puraspara que seja possiacutevel escrever programas de propoacutesito geral eacute necessaacuterio que uma linguagemtenha suporte agrave operaccedilotildees com efeito colateral Monads eacute um conceito que torna possiacutevel aexecuccedilatildeo de operaccedilotildees com efeito coleteral em linguagens puramente funcionais como Haskell

Monads pode ser visto como uma maneira de envolver o mundo Por exemplo ao inveacutesde se interpretar uma funccedilatildeo print como uma funccedilatildeo que recebe uma String e natildeo retornanenhum valor essa funccedilatildeo pode ser vista como uma funccedilatildeo que retorna um monad que conteacutemduas coisas

bull Um novo mundo que eacute basicamente o mundo antigo com a avaliaccedilatildeo da funccedilatildeo printaplicada agrave ele

bull O resultado puro de print que eacute a String que deve ser impressa na tela

1 printGreeting String -gt IO ()2 printGreeting name = putStrLn (Hey ++ name ++ you rock)3

4 main = do5 putStrLn Hello whatrsquos your name6 name lt- getLine7 printGreeting name

Coacutedigo 9 Exemplo do monad IO em Haskell

O exemplo mais conhecido de monad eacute o tipo IO Como eacute mostrado no Coacutedigo 9 a funccedilatildeoprintGreeting recebe uma String e retorna algo do tipo IO () Esse tipo de retornoeacute anaacutelogo ao tipo void de algumas linguagens imperativas jaacute que os parenteses vazios repre-sentam um tipo que tem apenas um valor que eacute ele mesmo Ainda nesse exemplo a notaccedilatildeodo eacute utilizada para agrupar um conjunto de operaccedilotildees monadicas que seratildeo executadas emsequecircncia Essa notaccedilatildeo eacute bastante conveniente pois representa bem a natureza imperativa docoacutedigo de entrada e saiacuteda

Aleacutem do conceito de monad resolver um problemada linguagem ele provecirc uma separaccedilatildeoclara entre o coacutedigo que conteacutem efeitos colaterais do que natildeo tem Isso eacute uma caracteriacutesticamuito interessante para programaccedilatildeo concorrente jaacute que funccedilotildees puras podem ser fortes candi-datas agrave paralelizaccedilatildeo uma vez que natildeo acessam nem alteram dados compartilhados

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 24: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

43 THREADS 15

43 Threads

Existem dois tipos de threads em Haskell Um deles eacute conhecido como threads do SO que satildeona verdade abstraccedilotildees das threads nativas do sistema operacional A utilizaccedilatildeo mais comumdesse tipo de thread em aplicaccedilotildees eacute criando-se tantas threads quantos satildeo os nuacutemeros denuacutecleos da CPU Utiliza-se a funccedilatildeo forkOS1 para criar esse tipo de thread

O outro tipo de thread eacute conhecido como threads de Haskell Satildeo bem mais leves queas threads do sistema operacional por necessitarem de menos recursos do sistema [21] Cadathread de Haskell executa em uma pilha privada de tamanho finito mas compartilha a mesmaheap com as demais threads Geralmente vaacuterias threads de Haskell executam em apenas umathread do sistema operacional Utiliza-se a funccedilatildeo forkIO2 para criar esse tipo de thread

main = doforkIO (replicateM_ 100000 (putChar rsquoArsquo))replicateM_ 100000 (putChar rsquoBrsquo)

Coacutedigo 10 Exemplo de utilizaccedilatildeo do forkIO

O Coacutedigo 10 exemplifica a utilizaccedilatildeo da funccedilatildeo forkIO No exemplo cria-se uma threadque imprime repetidamente (100000 vezes) o caracter A na saiacuteda do sistema enquanto a mainthread do programa imprime a mesma quantidade de vezes o caracter B Eacute esperado que sejaimpresso como saiacuteda desse programa caracteres A e B alternados

Eacute importante notar que ambos os tipos de threads satildeo gerenciadas por um escalonadorproacuteprio que faz parte da biblioteca de tempo de execuccedilatildeo de Haskell (Haskell Runtime Systemou RTS)

44 Memoacuteria Transacional em Haskell

O primeiro artigo que discute STM em Haskell foi publicado em 2005 por Harris et al [2]Neste artigo eacute descrito as principais caracteriacutesticas do sistema de memoacuteria transacional deHaskell Dentre essas caracteriacutesticas podemos destacar a utilizaccedilatildeo de controle de concorrecircn-cia otimista com atualizaccedilotildees tardias e a utilizaccedilatildeo um lock para garantir atomicidade durantea validaccedilatildeo de uma transaccedilatildeo

441 Variaacuteveis transacionais

As variaacuteveis transacionais de Haskell satildeo conhecidas como TVars A leitura e a escrita de umaTVar satildeo definidas pelas seguintes funccedilotildees

readTVar TVar a -gt STM awriteTVar TVar a -gt a -gt STM ()

1httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkOS2httpwwwhaskellorgghcdocslatesthtmllibrariesbaseControl-ConcurrenthtmlvforkIO

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 25: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 16

Como pode-se observar o retorno da funccedilatildeo readTVar eacute do tipo STM a Isso indicaque a funccedilatildeo readTVar retorna uma transaccedilatildeo (representada pelo monad STM) que quandoexecutada resultaraacute em um valor do tipo a Por conta do sistema de tipos de Haskell duascaracteriacutesticas importantes de uma transaccedilatildeo derivam dessas assinaturas

bull Nenhuma accedilatildeo STM pode ser executada fora de uma transaccedilatildeo Isso acontece porqueambas as funccedilotildees readTVar e writeTVar retornam um monad STM

bull Nenhum efeito colateral senatildeo a leitura e escrita em variaacuteveis transacionais pode aconte-cer dentro de uma transaccedilatildeo Isso pode ser obsevardo se for levado em conta que se umafunccedilatildeo chama uma outra funccedilatildeo que retorna o tipo IO por exemplo ela tem que retornartambeacutem o tipo IO Logo se fosse possiacutevel chamar uma funccedilatildeo que retorna um tipo IOdentro de uma transaccedilatildeo essa transaccedilatildeo deveria ser do tipo IO e natildeo STM

Essas duas consequecircncias satildeo muito importantes porque mostram uma caracteriacutestica muitoconveninente do sistema de memoacuteria transacional de Haskell o fato do sistema de tipos dalinguagem garantir que operaccedilotildees com efeito colateral natildeo podem acontecer dentro de umatransaccedilatildeo

442 Bloco atocircmico

O bloco atocircmico de Haskell eacute representado pela funccedilatildeo atomically

atomically STM a -gt IO a

Essa funccedilatildeo recebe uma transaccedilatildeo do tipo STM a e retorna uma accedilatildeo de IO que quandorealizada executa a transaccedilatildeo atomicamente em relaccedilatildeo agrave todas as outras transaccedilotildees e guarda ovalor retornado pela transaccedilatildeo

443 Bloqueio e escolha

Embora os blocos atocircmicos garantam a integridade dos dados da memoacuteria compartilhada du-rante a execuccedilatildeo de uma transaccedilatildeo eles satildeo bastante inadequados para coordenar programasconcorrentes [1] O sistema de STM de Haskell provecirc o operadores de bloqueio e escolha paraauxiliar nessa tarefa de coordenaccedilatildeo

Eacute muito comum em programas concorrentes se precisar de bloqueios Por exemplo emum buffer compartilhado que eacute utilizado em um modelo produtor-consumidor eacute necessaacuteriobloquear os consumidores que tentam realizar leitura do buffer vazio ateacute que novos elementossejam produzidos Esse tipo de comportamento pode ser alcanccedilado com a funccedilatildeo retry

retry STM a

Para exemplificar a utilizaccedilatildeo do retry considere o Coacutedigo 11 que conteacutem uma funccedilatildeoque bloqueia o produtor se o buffer estiver cheio O retry quando executado faz a transaccedilatildeoem questatildeo ser abortada e em algum momento re-executada A implementaccedilatildeo de Haskelltambeacutem garante que a re-execuccedilatildeo dessa transaccedilatildeo soacute iraacute acontecer quando o valor de alguma

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 26: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

44 MEMOacuteRIA TRANSACIONAL EM HASKELL 17

writeBuffer Buffer a -gt a -gt STM ()writeBuffer buffer value = do

if (isFull buffer)then retryelse (append buffer value)

Coacutedigo 11 Exemplo do uso do retry em Haskell

das variaacuteveis transacionais que estatildeo sendo lidas na transaccedilatildeo mudar [2] Isso permite que seevite re-execuccedilotildees desnecessaacuterias

Jaacute o operador de escolha eacute representado pela funccedilatildeo orElse

orElse STM a -gt STM a -gt STM a

Essa funccedilatildeo recebe duas transaccedilotildees como paracircmetro e permite ao usuaacuterio escolher uma accedilatildeoalternativa caso sua accedilatildeo principal falhe A ideia eacute que uma chamada como (orElse t1 t2)iraacute primeiro executar t1 se t1 em algum momento chamar retry t2 iraacute ser executada set2 tambeacutem chamar retry toda accedilatildeo seraacute re-executada

444 Implementaccedilatildeo da transaccedilatildeo bancaacuteria

O Coacutedigo 12 eacute uma possiacutevel implementaccedilatildeo para o problema da transaccedilatildeo bancaacuteria Nas linhas1 e 6 estatildeo representadas respectivamente as funccedilotildees de saque e depoacutesito Note que o retornode ambas as funccedilotildees eacute um tipo STM Isso garante que as duas funccedilotildees soacute podem ser chamadasdentro de uma transaccedilatildeo A funccedilatildeo transfer representada na linha 9 executa o depoacutesitoseguido do saque atomicamente por meio da funccedilatildeo atomically

1 withdraw Account -gt Int -gt STM ()2 withdraw acc amount = do3 balance lt- readTVar acc4 writeTVar acc (balance - amount)5

6 deposit Account -gt Int -gt STM ()7 deposit acc amount = withdraw acc (- amount)8

9 transfer Account -gt Account -gt Int -gt IO ()10 transfer from to amount = atomically $ do11 deposit to amount12 withdraw from amount

Coacutedigo 12 Exemplo da transaccedilatildeo bancaacuteria em Haskell

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 27: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

CAPIacuteTULO 5

Um Motor de Busca Paralelo com STM

Para entender melhor quais satildeo as diferenccedilas na praacutetica entre os modelos de STM de Clojuree Haskell foi escolhido como objeto de estudo a implementaccedilatildeo de um software natildeo trivialem ambas as linguagens Trata-se de um motor de busca paralelo para arquivos textuais Esseproblema foi extraiacutedo do trabalho de Pankratius e Adi-Tabatabai [22] onde ele eacute utilizado paracomparar a utilizaccedilatildeo de sincronizaccedilatildeo baseada em locks com a utilizaccedilatildeo de memoacuteria transa-cional Nesse experimento um grupo de alunos de poacutes-graduaccedilatildeo foi dividido em duplas ondeparte das duplas teve que desenvolver o motor de busca utilizando locks e outra parte utilizandomemoacuteria transacional ambos com a linguagem C++

Na primeira parte deste capiacutetulo o problema seraacute especificado Em seguida seraacute descritoem detalhes como a soluccedilatildeo do problema foi modelada e implementada nas duas linguagensE por fim seraacute levantado as principais diferenccedilas entre as duas implementaccedilotildees

51 Especificaccedilatildeo

O motor de busca deve atender aos seguintes requisitos [22]Indexaccedilatildeo O motor de busca deve funcionar apenas para arquivos texto e deve realizar a

pesquisa em arquivos que estejam em um diretoacuterio preacute-definido (passado como argumento pelalinha de comando) e em todos os seus subdiretoacuterios Natildeo eacute necessaacuterio que o iacutendice persistaem disco Diferentes estrateacutegias para criaccedilatildeo de iacutendices podem ser utilizadas Toda sequecircnciade caracteres natildeo-alfanumeacutericos eacute tratada como separadora de palavras Diferenccedilas entre letrasmaiuacutesculas e minuacutesculas e hifens satildeo ignorados Um indicador de progresso para indexaccedilatildeodeve mostrar a quantidade de bytes e arquivos processados ateacute entatildeo palavras encontradas ateacuteentatildeo e a quantidade de palavras no iacutendice A quantidade de threads de indexaccedilatildeo tambeacutem deveser configuraacutevel via paracircmetro de linha de comando

Busca Deve ser possiacutevel processar consultas enquanto a indexaccedilatildeo ainda estaacute em pro-gresso mas natildeo eacute necessaacuterio que mais de uma consulta seja executada ao mesmo tempo emparalelo Fica a cargo do desenvolvedor decidir se paraleliza cada consulta e a quantidade dethreads que devem ser utilizadas nesse processo Aleacutem disso a busca deve permitir diferentestipos de consultas

1 Consultas por passagens de texto coerentes (exemplo this is a text)2 Consultas com caracteres coringa no comeccedilo ou fim de uma palavra (exemplo houou

pa)3 Consultas que contenham uma seacuterie de palavras representando uma operaccedilatildeo AND (ex-

emplo tree house garden)

18

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 28: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

52 VERSAtildeO SEQUENCIAL 19

4 Consultas com exclusatildeo de palavras (exemplo -fruit)Saiacuteda A saiacuteda padratildeo do programa consiste na quantidade total de arquivos que os criteacuterios

de busca satildeo verdadeiros o tempo de consulta e a listagem dos nomes dos 50 primeiros arquivosordenados segundo os seguintes criteacuterios

1 Decrescentemente pela quantidade de ocorrecircncias de todos os criteacuterios consultados2 Lexicograficamente pelo nome do arquivoPode-se assumir que durante a execuccedilatildeo do programa nenhum arquivo envolvido na busca

seraacute alterado nem outros arquivos seratildeo adicionados ou removidos aos diretoacuterios envolvidos nabusca

52 Versatildeo sequencial

Inicialmente foi necessaacuterio implementar uma versatildeo sequencial do problema proposto para en-tender melhor o domiacutenio o qual o problema faz parte e tambeacutem para refletir sobre as estruturasde dados que poderiam ser utilizadas Essa primeira versatildeo foi escrita em Haskell Tambeacutem foiimplementada uma traduccedilatildeo dessa versatildeo em Clojure com propoacutesito de aprender a linguagemjaacute que o autor ainda natildeo tinha tido contato com essa linguagem nem com nenhum outro dialetode Lisp

521 Iacutendice

O primeiro passo do desenvolvimento foi decidir qual estrutura de dados seria utilizada paraarmazenar o iacutendice de palavras O principal objetivo do iacutendice eacute prover uma estrutura queassocie cada termo presente na base de documentos (o vocabulaacuterio) agrave suas ocorrecircncias nosdocumentos indexados

Para esse trabalho foi escolhida uma estrutura bastante utilizada em recuperaccedilatildeo de docu-mentos chamada de iacutendices invertidos Esse tipo de estrutura associa cada palavra do vocab-ulaacuterio agrave uma lista de documentos nos quais essa palavra estaacute presente Existem dois tipos deiacutendices invertidos os iacutendices invertidos agrave niacutevel de registro e os iacutendices invertidos agrave niacutevel depalavra [23] A diferenccedila entre eles eacute que o primeiro armazena apenas informaccedilatildeo sobre quaisdocumentos a palavra pertence enquanto o segundo guarda tambeacutem outras informaccedilotildees comoas posiccedilotildees que a palavra ocorre nos documentos Para realizar as consultas especificadas peloproblema foi necessaacuterio utilizar iacutendices invertidos agrave niacutevel de palavra

A escolha por utilizar iacutendices invertidos se baseou no fato de ser uma estrutura simplesque poderia ser representada de maneira faacutecil e eficiente em ambas a linguagens utilizandoas estruturas de dados providas pelas bibliotecas padratildeo Adicionalmente com a intenccedilatildeo demelhorar um pouco a performance das consultas foram utilizados um conjunto de iacutendicesinvertidos ao inveacutes de apenas um uacutenico iacutendice global onde cada iacutendice invertido armazenaapenas palavras iniciadas com uma determinada letra Dessa forma tem-se um iacutendice invertidopara cada caracter possiacutevel totalizando 35 iacutendices A Figura 51 conteacutem uma representaccedilatildeograacutefica do iacutendice No restante desse trabalho a palavra iacutendice seraacute utilizada para denotar aestrutura descrita neste paraacutegrafo e representada graficamente na Figura 51

Em Haskell o iacutendice foi representado utilizando-se um tipo Array (do moacutedulo DataArray)

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 29: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

52 VERSAtildeO SEQUENCIAL 20

Figura 51 Representaccedilatildeo do iacutendice

para armazenar todos os iacutendices invertidos e um Map (do moacutedulo DataMap) para cada iacutendiceinvertido O Array provecirc acesso O

(1)

aos seus elementos enquanto o Map provecirc acessoO(logn

)aos seus elementos Cada mapa eacute armazenado em uma posiccedilatildeo do array que corre-

sponde ao valor ASCII do caracter referente agrave primeira letra das palavras contidas no mapea-mento em questatildeo De maneira anaacuteloga em Clojure foram utilizados as estruturas vectore hash-map que provecircem a mesma complexidade de acesso aos elementos que as estruturasutilizadas em Haskell

522 Arquitetura

A versatildeo sequencial foi dividida em 5 moacutedulos Scanner Lexer Index Query e Main

Figura 52 Diagrama da arquitetura dos moacutedulos da versatildeo sequencial

Scanner Eacute responsaacutevel por percorrer recursivamente o diretoacuterio fornecido pelo usuaacuterio eseus subdiretoacuterios em busca de arquivos texto Esse moacutedulo basicamente recebe um caminhocomo paracircmetro e retorna o caminho de todos os arquivos texto achados

Lexer Eacute responsaacutevel por processar o texto lido de um documento Primeiramente aconteceo preacute-processamento do texto que eacute recebido como uma string uacutenica para remover carac-teres natildeo-alfanumeacutericos uniformizar a capitalizaccedilatildeo e quebrar a string de entrada em umalista de strings onde cada uma representa uma palavra Em seguida transforma essa listade palavras em um mapeamento onde cada palavra eacute associada a um conjunto de nuacutemeros querepresentam as posiccedilotildees que essa palavra aparece no documento Essa estrutura eacute chamada delista de ocorrecircncias

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 30: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

53 VERSAtildeO PARALELA 21

Index Eacute responsaacutevel por abstrair o acesso ao iacutendice Neste moacutedulo existem funccedilotildees paracriar um iacutendice inserir um documento (uma lista de ocorrecircncias) no iacutendice e buscar um termono iacutendice Esse moacutedulo eacute importante pois ele faz com que os demais moacutedulos natildeo precisem terconhecimento da estrutura interna do iacutendice Isso faz com que seja possiacutevel mudar a estruturade dados que eacute utilizada pelo iacutendice sem que o restante do programa precise ser modificado(desde que a API do moacutedulo seja mantida) Essa caracteriacutestica mostra que eacute possiacutevel se alcanccedilaruma modularizaccedilatildeo semelhante agraves linguagens orientadas agrave objeto em uma linguagem funcionalutilizando apenas estruturas de dados imutaacuteveis

Query Eacute responsaacutevel pela construccedilatildeo e execuccedilatildeo de consultas Esse moacutedulos tem funccedilotildeespara realizar o parsing da consulta fornecida pelo usuaacuterio e para executar uma dada consulta

Main Eacute o ponto de entrada do programa Ele que recebe e faz o parsing dos argumen-tos fornecidos pelo usuaacuterio chama as funccedilotildees de outros moacutedulos para obter os documentosprocessaacute-los e inseriacute-los no iacutendice Em seguida chama funccedilotildees do moacutedulo Query para realizara consulta e por fim imprime o resultado final com o ordenamento e a formataccedilatildeo correta parao usuaacuterio

53 Versatildeo paralela

A versatildeo paralela do motor de busca foi fortemente baseada na versatildeo sequecircncial Tanto aversatildeo paralela escrita em Haskell como a escrita em Clojure atendem agrave todos os pontos lis-tados na especificaccedilatildeo do problema com exceccedilatildeo dos tipos de consulta suportados Apenasas consultas por passagens de texto coerentes foram implementadas devido ao tempo reduzidodisponiacutevel para implementaccedilatildeo do problema em duas linguagens distintas Adicionalmenteembora natildeo fosse um requisito obrigatoacuterio as consultas satildeo executadas de foma paralela emambas as versotildees

531 Iacutendice

A estrutura do iacutendice em si eacute a mesma da versatildeo sequencial Poreacutem como deve ser possiacutevelexecutar consultas durante o processo de indexaccedilatildeo a utilizaccedilatildeo do iacutendice teve que ser repen-sada A razatildeo para isso eacute que na versatildeo sequencial eacute utilizado apenas um iacutendice global quearmazena os termos de toda base de documentos Se na versatildeo paralela essa mesma abordagemfosse utilizada a paralelizaccedilatildeo das consultas natildeo melhoraria o desempenho da aplicaccedilatildeo poisconsultas consecutivas iriam sempre ter resultados cumulativos Por exemplo se uma con-sulta for executada quando apenas 2 documentos foram indexados um resultado parcial X seraacuteproduzido Se a mesma consulta for executada novamente no futuro quando um total de 5documentos tiverem sido indexados o resultado produzido seraacute X + Y onde Y consiste no re-sultado da consulta nos 3 novos arquivos que foram indexados Dessa forma natildeo se tem ganhona paralelizaccedilatildeo das consultas

Para contornar esse problema existem duas teacutecnicas que podem ser aplicadas para realizar oprocessamento paralelo de consultas o particionamento de documentos e o particionamento determos [24] As duas abordagens dividem o iacutendice em subiacutendices por meio de uma n-particcedilatildeopor disjunccedilatildeo Na primeira abordagem cada subiacutendice conteacutem um subconjunto do conjunto de

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 31: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

53 VERSAtildeO PARALELA 22

todos os da base de documentos enquanto na segunda cada subiacutendice conteacutem um subconjuntodo conjunto de todos os termos Para o propoacutesito desse trabalho apenas o particionamento dedocumentos pode ser utilizado porque o particionamento de termos necessita de um conheci-mento preacutevio da base de documentos

Figura 53 Representaccedilatildeo das duas abordagens de particionamento de iacutendice

Assim para resolver o problema da consulta durante a indexaccedilatildeo satildeo utilizados vaacuteriossubiacutendices onde cada um tem um nuacutemero maacuteximo de documentos que podem ser indexadosQuando um subiacutendice atinge o nuacutemero maacuteximo de documentos ele estaacute pronto para ser con-sultado Tanto o nuacutemero maacuteximo de documentos por subiacutendice quanto a quantidade inicial desubiacutendices criados podem ser configurados pelo usuaacuterio por meio de argumentos de linha decomando

532 Arquitetura

Alguns moacutedulos utilizados na versatildeo sequencial foram mantidos sem modificaccedilotildees como foio caso do Lexer Index e Query Poreacutem para atender aos requisitos de paralelizaccedilatildeo osdemais moacutedulos tiveram que ser modificados e outros tiveram que ser criados

Scanner Esse moacutedulo teve que ser mudado para que a busca por documentos aconte-cesse em uma thread separada Assim a descoberta de arquivos eacute executada paralelamente aoprocessamento e a indexaccedilatildeo dos documentos Esse comportamento caracteriza um modeloprodutor-consumidor onde o Scanner eacute o produtor e as threads de processamento e index-accedilatildeo satildeo os consumidores Tanto na versatildeo Clojure como na versatildeo Haskell esse modelo eacuteimplementado por meio de um buffer compartilhado de capacidade infinita que bloqueia osconsumidores que tentam ler do buffer vazio ateacute que novos elementos sejam produzidos

Engine Esse moacutedulo eacute responsaacutevel pelo orquestramento das threads de processamentoe indexaccedilatildeo de documentos e tambeacutem das threads de consulta Inicialmente satildeo criadas nthreads1 responsaacuteveis pelo processamento e indexaccedilatildeo dos documentos Cada thread executaem loop enquanto houverem arquivos para serem processados Esses arquivos satildeo lidos do

1n eacute fornecido pelo usuaacuterio

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 32: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

53 VERSAtildeO PARALELA 23

Figura 54 Diagrama simplificado de como eacute feita a comuniccedilatildeo entre threads

buffer compartilhado com o Scanner Para cada arquivo que eacute lido ele eacute processado e emseguida indexado em um dos subiacutendices disponiacuteveis Apoacutes a indexaccedilatildeo se o subiacutendice estivercheio (jaacute tiver completado o nuacutemero maacuteximo de documentos por subiacutendice) ele eacute marcadocomo pronto e um novo subiacutendice vazio eacute adicionado agrave lista de subiacutendices disponiacuteveis Esse eacuteo funcionamento das threads de processamento e indexaccedilatildeo Jaacute a parte das consultas funcionamda seguinta forma sempre que um subiacutendice eacute marcado como pronto uma nova thread eacute criadapara cada consulta fornecida pelo usuaacuterio Cada thread de consulta vai executar a consulta nosubiacutendice e concatenar o resultado em uma variaacutevel transacional que eacute utilizada para acumularo resultado dessa consulta Ao final quando todos os subiacutendices tiverem sido consultados cadavariaacutevel transacional teraacute o resultado completo de uma consulta

Buffer Esse moacutedulo abstrai o acesso aos buffers que satildeo compatilhados entre threadsConteacutem funccedilotildees para criaccedilatildeo adiccedilatildeo e remoccedilatildeo de elementos

Logger Eacute responsaacutevel por gerenciar o indicador de progresso do programa Ele roda emuma thread separada e fica recebendo mensagens de status dos demais moacutedulos informandoo progresso Baseado nesses status as mensagens de indicaccedilatildeo de progresso satildeo formatadas eimpressas no console para o usuaacuterio durante a execuccedilatildeo do programa

Main Como o controle do processamento dos arquivos e das consultas foi delegado aomoacutedulo Engine o Main na versatildeo paralela apenas faz o parsing dos paracircmetros fornecidospelo usuaacuterio e inicia o Scanner o Engine e o Logger

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 33: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

CAPIacuteTULO 6

Resultados

Neste capiacutetulo inicialmente seraacute analisada as principais diferenccedilas entre as implementaccedilotildees emClojure e Haskell Em seguida seraacute descrito em detalhes como foram realizados os experi-mentos para comparar as duas implementaccedilotildees em termos de desempenho e complexidade docoacutedigo

61 Comparaccedilatildeo das implementaccedilotildees

O principal ponto de divergecircncia entre as duas implementaccedilotildees estaacute relacionado ao fato domodelo de STM de Clojure natildeo prover o operador de bloqueio Como em Haskell existe esseoperador (a funccedilatildeo retry) todo compartilhamento de memoacuteria e sincronizaccedilatildeo das threadspocircde ser feito utilizando apenas memoacuteria transacional

Em decorrecircncia dessa limitaccedilatildeo alguns dos mecanismos utilizados na implementaccedilatildeo emHaskell tiveram que ser reestruturados na implementaccedilatildeo em Clojure O principal exem-plo dessa diferenccedila eacute o modelo produtor-consumidor descrito atenriormente que foi bas-tante utilizado na soluccedilatildeo proposta para compartilhar memoacuteria e sincronizar as threads EmHaskell foram utilizados buffers compartilhados entre as threads para realizar essa tarefaEsses buffers foram contruiacutedos utilizando TChans1 que internamente satildeo modelados uti-lizando duas TVars A principal funccedilatildeo desses buffers eacute aleacutem de compartilhar memoacuteria entreas threads bloquear os consumidores que tentam realizar leitura quando o buffer estaacute vazio

Em Clojure a uacutenica maneira se simular exatamente o mesmo funcionamento do modeloprodutor-consumidor utilizado em Haskell eacute atraveacutes da classe LinkedBlockingQueue2

de Java ou semelhantes Essa classe eacute implementa utilizando locks (mais especificamenteReentrantLock3) para garantir o bloqueio dos consumidores A utilizaccedilatildeo dessa classe foiminimizada ao maacuteximo com o objetivo utilizar na soluccedilatildeo implementada em Clojure as con-struccedilotildees que satildeo providas diretamente pela linguagem Dessa forma apenas o buffer utilizadopara compartilhar os subiacutendices disponiacuteveis entre as threads de processamento e indexaccedilatildeoforam modelos por meio da classe LinkedBlockingQueue

As demais utilizaccedilotildees do modelo produtor-consumidor foram representadas em Clojurecomo uma comunicaccedilatildeo assiacutencrona por meio de notificaccedilotildees Para isso foram utilizados osagentes descritos na Seccedilatildeo 33 Os agentes provecircem uma unidade de memoacuteria compartilhadaque eacute atualizada atraveacutes de accedilotildees assiacutencronas executadas em uma thread separada Aleacutem disso

1httphackagehaskellorgpackagesarchivestm242dochtmlControl-Concurrent-STM-TChanhtml2httpdocsoraclecomjavase7docsapijavautilconcurrentLinkedBlockingQueuehtml3httpdocsoraclecomjavase7docsapijavautilconcurrentlocksReentrantLockhtml

24

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 34: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

61 COMPARACcedilAtildeO DAS IMPLEMENTACcedilOtildeES 25

eacute possiacutevel registrar funccedilotildees a um agente conhecidas como watchers4 que seratildeo chamadassempre que o valor do agente mudar (semelhante a um mecanismo de callbacks) A comu-nicaccedilatildeo com o Logger por exemplo foi modelada dessa forma Ao inveacutes de manter umathread dedicada que fica bloqueada esperando novas mensagens serem adicionadas ao bufferum agente eacute utilizado para guardar as mensagens enviadas ao Logger Dessa forma para en-viar uma mensagem para o Logger a thread remetente deve enviar uma accedilatildeo de append juntocom mensagem em questatildeo para um agente especiacutefico que eacute executada assincronamente emoutra thread gerenciada pelo runtime de Clojure Depois quando essa accedilatildeo for executada umafunccedilatildeo especiacutefica que foi registrada como watcher do agente do Logger seraacute responsaacutevelpor processar a mensagem inserida e imprimiacute-la com a formataccedilatildeo correta para o usuaacuterio

Outra divergecircncia indiretamente relacionada ao fato de Clojure natildeo prover o operador debloqueio foi notada apoacutes o teacutermino das implementaccedilotildees A quantidade de transaccedilotildees emmemoacuteria que realizam mais de uma operaccedilatildeo eacute consideravelmente reduzida na implementaccedilatildeoem Clojure ao comparar-se agrave implementaccedilatildeo em Haskell Isso aconteceu porque a maioria dastransaccedilotildees que tinham essa caracteriacutestica na implementaccedilatildeo em Haskell estavam relacionadasao buffer compartilhado entre as threads

data Buffer a = Buffer (TChan a) (TVar Int) (TVar Bool)

newEmptyBuffer STM (Buffer a)readBuffer Buffer a -gt STM (Maybe a)writeBuffer Buffer a -gt a -gt STM ()enableFlag Buffer a -gt STM ()readFlag Buffer a -gt STM Bool

Coacutedigo 13 API do moacutedulo Buffer da implementaccedilatildeo em Haskell

Para deixar essa relaccedilatildeo mais clara considere a API do moacutedulo Buffer mostrada noCoacutedigo 13 Primeiro note que um Buffer eacute representado atraveacutes de um TChan que eacute o bufferde fato um TVar Int que manteacutem a quantidade de elementos armazenados no momento eum TVar Bool que eacute uma flag indicando se a produccedilatildeo jaacute terminou Analizando a assinaturados tipos das funccedilotildees desse moacutedulo tambeacutem eacute faacutecil de perceber que toda manipulaccedilatildeo doBuffer soacute pode ser feita dentro de uma transaccedilatildeo Como o Buffer eacute representado como umtipo composto e todos os valores que ele conteacutem satildeo variaacuteveis transacionais todas as operaccedilotildeesdesse moacutedulo por si soacute jaacute executam mais de uma operaccedilatildeo por transaccedilatildeo Por exemplo sempreque um elemento eacute adicionado ao buffer aleacutem desse elemento ser adicionado ao TChan ocontador tambeacutem deve ser incrementado caracterizando uma operaccedilatildeo composta jaacute que outrasthreads natildeo devem ter acesso ao estado em que um elemento foi adicionado ao buffer e ocontador ainda natildeo foi incrementado Na utilizaccedilatildeo dessa API tambeacutem eacute necessaacuterio utilizarcomposiccedilatildeo de operaccedilotildees quando a produccedilatildeo termina geralmente quando o produtor sabeque a produccedilatildeo acabou ele executa uma transaccedilatildeo que adiciona o uacuteltimo elemento ao buffer eao mesmo tempochama a funccedilatildeo enableFlag para informar as threads consumidoras que

4httpclojuregithubioclojureclojurecore-apihtmlclojurecoreadd-watch

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 35: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

62 EXPERIMENTOS 26

a produccedilatildeo terminou Dessa forma os consumidores conseguem ser informados quando suasexecuccedilotildees devem terminar

De forma resumida essas satildeo as principais diferenccedilas entre as duas implementaccedilotildees Ex-istem outros pontos de divergecircncia mas que natildeo satildeo diretamente relacionados agrave maneira queo problema foi modelado e implementado mas sim com a diferenccedila entre as linguagens quesatildeo diversas desde o funcionamento do sistema de tipos ateacute como as APIs fornecidas pelasbibliotecas padratildeo satildeo projetadas

62 Experimentos

Para analisar o desempenho das implementaccedilotildees em Clojure e Haskell foi utilizada uma basede documento composta de 60 livros de domiacutenio puacuteblico em lingua inglesa que foram obtidospor meio do projeto Gutenberg5 totalizando 36 megabytes de arquivos texto Foram utilizadastambeacutem seis consultas com tamanhos distintos e ocorrecircncias variadas na base de documen-tos Os experimentos foram executados em uma maacutequina com processador Intel Core i7-377034GHz com 4 nuacutecleos fiacutesicos e 8 gigabytes de memoacuteria RAM rodando Linux Para o ambienteHaskell foi utilizado o compilador GHC 762 junto com a flag de compilaccedilatildeo -O Jaacute para oambiente Clojure foi utilizado Clojure 151 com OpenJDK 7u17

Todo o coacutedigo produzido neste trabalho eacute aberto e estaacute disponiacutevel junto com o histoacuterico dedesenvolvimento no GitHub67

10

15

20

25

30

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

ClojureHaskell

Figura 61 Graacutefico do desempenho das versotildees paralelas em Clojure e Haskell

A Figura 61 mostra um graacutefico onde o eixo horizontal representa a quantidade de threads

5httpwwwgutenbergorg6httpsgithubcomluisgabrieltsearch7httpsgithubcomluisgabrieltsearch-clj

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 36: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

62 EXPERIMENTOS 27

passada como argumento para o programa e o eixo vertical a duraccedilatildeo em segundos da execuccedilatildeodo programa Cada ponto do graacutefico representa a meacutedia do tempo de execuccedilatildeo obtido a partirde 10 execuccedilotildees consecutivas do programa calculado por meio do comando time do Unix

Como pode-se perceber o desempenho de ambas as implementaccedilotildees foi bastante semel-hante As duas apresentaram melhora do desempenho total do sistema com o aumento donuacutemero de threads Poreacutem a melhoria de desempenho natildeo foi grande se comparada ao au-mento nos recursos de processamento disponiacuteveis

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Haskell

0

5

10

15

20

25

30

35

40

sequencial 2 4 6 8 10

Dura

cao (

segundos)

Clojure

Figura 62 Graacuteficos de comparaccedilatildeo de desempenho entre das versotildees sequenciais e paralelas

Algumas caracteriacutesticas de cada implementaccedilatildeo podem ter contribuiacutedo para que o aumentode desempenho natildeo fosse tatildeo alto Na implementaccedilatildeo em Haskell por exemplo utilizou-sememoacuteria transacional de forma excessiva inclusive em cenaacuterios onde natildeo haviam operaccedilotildeescompostas Dado que existe um overhead relacionado agrave manutenccedilatildeo de logs e sincronizaccedilatildeodas transaccedilotildees eacute possiacutevel que esse fato tenha contribuiacutedo para degradar o desempenho totaldo sistema Jaacute na versatildeo Clojure pode-se citar a utilizaccedilatildeo de threads do sistema operacionalao inveacutes de green threads como um fato que pode ter contribuiacutedo para a pequena melhora nodesempenho do programa Estudos subsequumlentes precisaratildeo analisar com mais profundidadeos fatores que acarretaram no comportamento natildeo-escalaacutevel dessas implementaccedilotildees

A Figura 62 por sua vez mostra os mesmos dados do graacutefico anterior com a adiccedilatildeo dosresultados obtidos da execuccedilatildeo dos mesmos experimentos com as implementaccedilotildees sequenciaisem cada linguagem Com esses graacuteficos pode-se notar que o principal objetivo da paraleliza-ccedilatildeo do problema foi alcanccedilado jaacute que a versatildeo paralela em todas as configuraccedilotildees testadasconseguiu ter desempenho melhor que a versatildeo sequencial

Tambeacutem eacute importante observar que foi necessaacuterio aplicar apoacutes finalizaccedilatildeo da implemen-taccedilatildeo algumas otimizaccedilotildees ao coacutedigo em Clojure para que fosse possiacutevel atingir o desempenhoapresentado nas Figuras 61 e 62 Como pode-se verificar na Figura 63 a diferenccedila de desem-penho com e sem otimizaccedilatildeo eacute muito grande A principal causa dessa diferenccedila estaacute atreladaao uso de reflexatildeo por parte do compilador de Clojure para realizar chamadas de meacutetodosJava Para que o coacutedigo desenvolvido nesse trabalho tivesse um desempenho aceitaacutevel foinecessaacuterio utilizar anotaccedilotildees de tipos em pontos do coacutedigo onde meacutetodos Java eram chamadosdiretamente Esse resultado eacute importante pois mostra que apesar das vantagem impostas pela

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 37: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

62 EXPERIMENTOS 28

20

40

60

80

100

120

140

160

2 4 6 8 10

Dura

cao (

segundos)

Quantidade de threads de indexacao

Clojure com otimizacaoClojure sem otimizacao

Figura 63 Graacutefico de comparaccedilatildeo das versotildees em Clojure com e sem otimizaccedilatildeo

tipagem dinacircmica de Clojure o desempenho da linguagem eacute fortemente afetado pela presenccedilade informaccedilatildeo de tipo em tempo de compilaccedilatildeo

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Haskell

0

100

200

300

400

500

sequencial paralela

Lin

has d

e C

odig

o

Clojure

Figura 64 Graacuteficos de comparaccedilatildeo da quantidade de linhas de coacutedigo

Por fim a Figura 64 mostra o comparativo entre a quantidade de linhas de coacutedigo total detodas as implementaccedilotildees Como pode-se perceber a paralelizaccedilatildeo implicou em um aumentoconsideraacutevel na quantidade de linhas de coacutedigo dos projetos No caso de Clojure foi umaumento de aproximadamente 120 enquanto em Haskell foi um aumento de 200 Esseresultado mostra que de fato paralelizar um programa implica no aumento da complexidadeda soluccedilatildeo Em um ambiente industrial se faz necessaacuterio uma anaacutelise mais minuciosa paraentender a relaccedilatildeo de custo-benefiacutecio de se escrever sistemas concorrentes pois dependendodo cenaacuterio melhorar o desempenho de um sistema em torno de 40 ao custo de duplicar outriplicar a base de coacutedigo pode ser inaceitaacutevel

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 38: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

CAPIacuteTULO 7

Conclusatildeo

Nesse trabalho foi apresentado como funciona e quais os requisitos necessaacuterio para implemen-taccedilatildeo de um sistema Memoacuteria Transacional em Software bem como a forma com que essemodelo eacute aplicado nas linguagens funcionais Clojure e Haskell Por meio da implementaccedilatildeode um motor de busca paralelo foi possiacutevel entender na praacutetica como fazer uso de memoacuteriatransacional para resolver um problema natildeo trivial

De uma forma geral a utilizaccedilatildeo de memoacuteria transacional se mostrou como uma formabastante natural de se lidar com memoacuteria compartilhada em um sistema concorrente Agruparum conjunto de operaccedilotildees para serem executadas de maneira atocircmica parece ser uma formamais intuitiva de manter a consistecircncia dos dados se comparado agrave proteccedilatildeo de accesso por meioda utilizaccedilatildeo de locks e variaacuteveis condicionais

Tambeacutem eacute importante notar que a utilizaccedilatildeo de linguagens funcionais para implementarsistemas concorrentes eacute algo bastante viaacutevel Lidar com dados imutaacuteveis isenta o desenvolvedorde muitas preocupaccedilotildees relacionadas agrave concorrecircncia E como foi mostrado no Capiacutetulo 5 eacutepossiacutevel organizar o coacutedigo de maneira modularizada e se obter benefiacutecios equiparaacuteveis aosobtidos em linguagens orientadas agrave objetos no que se refere agrave modularidade do coacutedigo

As duas linguagens utilizadas nesse estudo embora bastante distintas entre si se mostrarammuito flexiacuteveis para o tipo de tarefa que foram utilizadas Haskell tem a vantagem de ter umforte background acadecircmico que embasa grande parte das decisotildees de projeto da linguagem ereflete em mecanismos extremamente sofisticados e robustos como eacute o caso do seu sistema detipos Clojure por sua vez eacute uma linguagem que vem ganhando muito adeptos nos uacuteltimosanos e tem como principal vantagem a integraccedilatildeo com a maacutequina virtual de Java fazendo comque seja possiacutevel utilizar uma gama de bibliotecas bastante atestadas de maneira muito simplesatraveacutes de uma integraccedilatildeo fluiacuteda com a linguagem

Os resultados obtidos nesse trabalho tambeacutem mostraram que apesar da utilizaccedilatildeo de memoacuteriatransacional e linguagens funcionais facilitar o desenvolvimento de sistemas concorrentes ex-iste uma complexidade intriacutenseca agrave esse tipo de sistema que natildeo pode ser desconsideradaAinda eacute necessaacuterio muito estudo nessa aacuterea para que seja possiacutevel diminuir a complexidade dese projetar e implementar soluccedilotildees que tirem bom proveito das arquiteturas multicore

Em trabalhos futuros o primeiro passo seraacute implementar os outros tipos de consultas paraque as implementaccedilotildees atendam agrave todos os requisitos da especificaccedilatildeo apresentada na Seccedilatildeo51 Tambeacutem pode-se investigar outras estruturas para representaccedilatildeo do iacutendice com a intenccedilatildeode melhorar o desempenho das consultas e aumentar a concorrecircncia (menos bloqueios) durantea indexaccedilatildeo Por fim para fazer um estudo mais abrangente dos sistemas de STM de Clojuree Haskell seria interessante utilizar como objeto de estudo um problema que envolva maisoperaccedilotildees compostas em sua regra de negoacutecios

29

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 39: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

Referecircncias Bibliograacuteficas

[1] S P Jones ldquoBeautiful concurrencyrdquo Beautiful Code Leading Programmers Explain HowThey Think (Theory in Practice (OrsquoReilly)) OrsquoReilly Media Inc pp 385ndash406 2007

[2] T Harris S Marlow S Peyton-Jones and M Herlihy ldquoComposable memory transac-tionsrdquo in Proceedings of the tenth ACM SIGPLAN symposium on Principles and practiceof parallel programming ACM 2005 pp 48ndash60

[3] T Economist (2011) Parallel bars [Online] Available httpwwweconomistcomnode18750706

[4] H News (2012) Poll Whatrsquos your favorite programming language [Online] Availablehttpsnewsycombinatorcomitemid=3746692

[5] G Agha ldquoActors a model of concurrent computation in distributed systemsrdquo MIT Pressvol 11 no 12 p 12 1986

[6] N Shavit and D Touitou ldquoSoftware transactional memoryrdquo in Proceedings of the four-teenth annual ACM symposium on Principles of distributed computing ACM 1995 pp204ndash213

[7] D B Lomet ldquoProcess structuring synchronization and recovery using atomic actionsrdquoin ACM Sigplan Notices vol 12 no 3 ACM 1977 pp 128ndash137

[8] R Elmasri and S B Navathe Fundamentals of Database Systems (5th Edition) AddisonWesley March 2006

[9] T Harris J Larus and R Rajwar ldquoTransactional memoryrdquo Synthesis Lectures on Com-puter Architecture vol 5 no 1 pp 1ndash263 2010

[10] A Silberschatz H F Korth and S Sudarshan Database system concepts McGraw-HillHightstown 1997 vol 4

[11] E Helin and H Rodrick ldquoEfficiency of software transactional memoryrdquo 2010

[12] M F Spear V J Marathe W N Scherer III and M L Scott ldquoConflict detectionand validation strategies for software transactional memoryrdquo in Distributed ComputingSpringer 2006 pp 179ndash193

[13] C Herzeel P Costanza and T DrsquoHondt ldquoReusable building blocks for software transac-tional memoryrdquo in Proceedings of the 2nd European Lisp Symposium Milan 2009

30

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo
Page 40: Um Estudo Comparativo de Linguagens Funcionais para ...tg/2012-2/lgnfl.pdf · Os processadores multicore estão presentes em praticamente todos os computadores modernos, inclusive

REFEREcircNCIAS BIBLIOGRAacuteFICAS 31

[14] R Hickey (2010) Clojure rationale [Online] Available httpclojureorgrationale

[15] M Volkmann (2009) Software transactional memory [Online] Available httpjavaociwebcommarkstmarticlehtml

[16] R Hickey (2010) Clojure refs [Online] Available httpclojureorgrefs

[17] N Swinnerton (2012) Clojure stm - what why how [Online] Availablehttpsw1nncomblog20120411clojure-stm-what-why-how

[18] Wikipedia (2013) Snapshot isolation [Online] Available httpenwikipediaorgwindexphptitle=Snapshot_isolationampoldid=533821759

[19] B OrsquoSullivan J Goerzen and D B Stewart Real World Haskell OrsquoReilly Media2008

[20] S P Jones D Vytiniotis S Weirich and M Shields ldquoPractical type inference forarbitrary-rank typesrdquo Journal of Functional Programming vol 17 no 1 p 1 2007

[21] S Marlow S Peyton Jones and S Singh ldquoRuntime support for multicore haskellrdquo inACM Sigplan Notices vol 44 no 9 ACM 2009 pp 65ndash78

[22] V Pankratius and A-R Adl-Tabatabai ldquoA study of transactional memory vs locks inpracticerdquo in Proceedings of the 23rd ACM symposium on Parallelism in algorithms andarchitectures ACM 2011 pp 43ndash52

[23] R Baeza-Yates B Ribeiro-Neto et al Modern information retrieval ACM press NewYork 1999 vol 463

[24] S Buumlttcher C Clarke and G V Cormack Information retrieval Implementing andevaluating search engines The MIT Press 2010

  • Introduccedilatildeo
  • Memoacuteria Transacional em Software
    • Transaccedilotildees
    • Bloco atocircmico
    • Composabilidade
    • Controle de Concorrecircncia
    • Estrateacutegias de Atualizaccedilatildeo e Versionamento
    • Outros Aspectos
      • Clojure
        • Conceitos baacutesicos
        • Interoperabilidade com Java
        • Threads
        • Memoacuteria Transacional em Clojure
          • Controle de Concorrecircncia
          • Variaacuteveis transacionais
          • Blocos atocircmicos
          • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
              • Haskell
                • Conceitos baacutesicos
                • Monads
                • Threads
                • Memoacuteria Transacional em Haskell
                  • Variaacuteveis transacionais
                  • Bloco atocircmico
                  • Bloqueio e escolha
                  • Implementaccedilatildeo da transaccedilatildeo bancaacuteria
                      • Um Motor de Busca Paralelo com STM
                        • Especificaccedilatildeo
                        • Versatildeo sequencial
                          • Iacutendice
                          • Arquitetura
                            • Versatildeo paralela
                              • Iacutendice
                              • Arquitetura
                                  • Resultados
                                    • Comparaccedilatildeo das implementaccedilotildees
                                    • Experimentos
                                      • Conclusatildeo