• Nenhum resultado encontrado

Utilização de grafo não bloqueante em programação paralela

N/A
N/A
Protected

Academic year: 2021

Share "Utilização de grafo não bloqueante em programação paralela"

Copied!
6
0
0

Texto

(1)

Utilização de grafo não bloqueante em programação paralela

Israel Silva Barbara, Guilherme P. Britto Cousin, Rodolfo M. Favaretto,

Gerson Geraldo H. Cavalheiro

1Centro de Desenvolvimento Tecnológico – CDTec

Universidade Federal de Pelotas – UFPel Pelotas, RS – Brasil

{isbarbara, gpbcousin, rmfavaretto, gerson.cavalheiro,}@inf.ufpel.edu.br

Abstract. This paper presents a case study of the usage of atomic structures to implement procedures to handle graphs concurrently in a non-blocking way. The performance of this solution is compared to other two implementations, one based on the use of binary semaphore (mutex) synchronization mechanism and the other based on the use of transactional memories resources. The results shows that our method provide shorter execution times

Resumo. Este artigo aborda o uso de estruturas atômicas na implementação de rotinas para manipulação de um grafoP de forma concorrente e não bloqueante. A implementação é comparada, em termos de desempenho, com implementa-ções equivalentes utilizando mecanismo de controle de seção crítica baseado em semáforos binários (mutexes) e memórias transacionais. Os resultados ob-tidos apontam que a solução proposta apresenta resultados de desempenho sa-tisfatórios quando comparada aos demais métodos.

1. Introdução

Sendo reflexo da constante evolução de arquiteturas para processamento paralelo, e da consequente necessidade de explorar eficientemente seus recursos, a implementação de software paralelo tem evoluído significativamente. Um dos aspectos que deve ser consi-derado com vistas a obter desempenho da aplicação é garantir que todos os processadores disposívies estejam ativos a maior parte do tempo possível. A ociosidade de um dos pro-cessadores acarreta em uma perda significativa de desempenho uma vez que representa o não atendimento às demandas de processamento da aplicação. Um caso onde o proces-sador pode entrar em estado ocioso é reflexo da sincronização de dados entre diferentes threads concorrentes. Neste caso observa-se geração de sobrecusto para a execução do programa no momento de atender a sincronização requisitada. Mecanismos de controle ao acesso aos dados compartilhado, podem portando, serializar a execução do programa quando não construídos da maneira correta influenciando tempo total de execução.

Este trabalho aborda a concepção de um conjunto de rotinas para manipulação de um grafo de forma não bloqueante em um programa multithread, visando a redução do acesso a seção crítica, com a utilização de mecanismos não bloqueante, para o controle de acesso a seção crítica. A utilização das estruturas não bloqueantes são extremamente eficientes para aplicações específicas [Farook and Graham 1998].

(2)

2. Mecanismos de Controle a seção crítica

Para que um programa multithread mantenha a sua consistência, mecanismos de sincroni-zação devem ser utilizados para coordenar acessos a dados compartilhados entre threads. Sincronização também pode ser utilizada para ter controle do fluxo de execução dos th-readsem algum ponto da execução. A não utilização da sincronização pode gerar uma inconsistência nos dados ao final da execução do programa.

2.1. Exclusão Mútua

Exclusão Mútua (mutex), é um tipo especial de semáforo onde somente um thread ter acesso a uma determinada região de memória. Duas operações são possíveis: lock e unlock, para adquirir e liberar o acesso, respectivamente. Caso um thread possua um lock e outro thread tenta acessar a mesma região de memória, o segundo é bloqueado pelo primeiro. Este bloqueio causa um overhead para o programa em função da troca de contexto que ocorre com o thread bloqueado. Existem duas perdas relacionadas à utilização de mutex, a serialização e a troca de contexto.

Com o intuito de diminuir os tempos associados às trocas de contexto tem se como opção o spinlock, que é um tipo de mutex no qual o thread, ao invés de fazer uma troca de contexto, executa num laço de repetição onde é checado se o mutex está liberado ou não, este tipo de mutex tem um melhor desempenho quando a seção crítica é pequena, pois quando o método que utiliza spinlock gasta ciclos de processador e se a seção crítica for longa o núcleo ficará inativo ao não executar novas instruções que alterem o estado do programa, o que não é desejável em um contexto multicore. A análise sobre como o thread executa na seção crítica torna a programação com mutex uma tarefa mais complexa, levando em conta que o programador tem que analisar e decidir qual a estratégia é a mais adequada ao problema abordado.

2.2. Algoritmos não bloqueante

Recursos de sincronização clássicos, como mutex, foram concebidos em uma realidade na qual abstrações para programação concorrente não se faziam tão necessárias quanto na realidade presente onde imperam arquiteturas baseadas em processadores multicore. A estes recursos clássicos surgem alternativas, graças aos avanços nas arquiteturas e a novos modelos de programação.

Mecanismos não bloqueantes são de uma grande complexidade na implementa-ção, e seu emprego requer do programador grandes conhecimentos do compilador e da arquitetura, entretanto se implementados da maneira correta pode gerar um ganho bastante significativo em relação aos demais mecanismos de controle ao acesso a dados comparti-lhados. Para a implementação deste mecanismos é necessário a utilização de instruções atômicas. Uma instrução atômica é conhecida por ser uma sequência de uma ou mais instruções de máquina que executam sequencialmente sem interrupção.

Por padrão, qualquer sequência de duas ou mais instruções não pode ser consi-derado atômica, já que o sistema operacional pode decidir por preemptar um thread em favor de outro. Para garantir que a operação seja atômica, o programador deve utilizar instruções de alto nível que possam ser resolvidas para uma única instrução de máquina, assim garantindo atomicidade, isto ocorre dado ao fato de que uma única instrução não pode ser interrompida na metade.

(3)

Processadores possuem instruções chamadas primitivas atômicas que podem ser usadas na construção de algoritmos lock-free e wait-free, estas operações podem ser do tipo read-modify-write(RMW), nas quais uma única operação lê o valor da memória para um registrador, opera sobre este e o armazena na memória, ou operações do tipo Load e Storeatômica , as operações mais comuns são:

• Atomic load, faz a leitura em tempo atômico de uma valor da memória para o registrado;

• Atomic store, faz a escrita de um valor do registrador para a memória; • Test-and-set, escreve um valor na memória e retorna o valor anterior;

• Fetch-and-add, lê um valor para o registrador adiciona um valor e armazena este na memória;

• Compare-and-swap, lê o valor para a memória e compara com um segundo valor, caso os valores sejam iguais, um terceiro valor é escrito na memória;

Assim, para o problema do contador global, temos uma solução onde não preci-samos criar uma seção explícita de controle de regiões onde possa ocorrer condições de corrida, basta que as instruções relacionadas as variáveis que fazem alterações no código sejam feitas por operações indivisíveis.

2.3. Memórias transacionais

Como alternativa à difícil implementação da seção crítica por meio de mutex e algoritmos não bloqueantes, apresentam-se as memórias transacionais, as quais possuem alto nível de abstração para compartilhamento de dados. Com a crescente percepção de que atrasos imprevisíveis são grande problema em arquiteturas paralelas, argumenta-se que técnicas convencionais para acesso a dados compartilhados por meio de seção crítica é inadequado, dado que este limita paralelismo, aumenta a contenção de memória e faz o sistema ficar vulnerável a falhas do processador e outras anomalias. [Shavit and Touitou 1997]

Operações atômicas evitam problemas relacionados com o tratamento convencio-nal de seções críticas como deadlock, inversão de prioridade troca de contexto, ao custo de ser uma solução especializada, e utilizando instruções de baixo nível o que aumenta significativamente a complexidade do uso de tal técnica. Memórias transacionais são uma solução para o uso de técnicas lock-free, trazendo o desempenho e a escalabilidade de operações atômicas, com a mesma facilidade de trabalhar com o modelo clássico mas sem os problemas gerados pelo uso deste.

Uma transação consiste em uma sequência de uma ou mais instruções de leitura e escrita à memória executadas no contexto de um thread [Herlihy and Moss 1993]. Uma transação deve satisfazer duas propriedades. (1) Isolamento, transações não observam o estado intermediário de outras transações, assim parecendo que estas executam em serial. (2) Atomicidade, cada transação faz uma sequência de tentativas de escrita na memória compartilhada, quando esta transação é concluída, pode ocorrer um commit em tempo atômico, fazendo com o que as outras thread possam ter visibilidade da sua área de escrita, ou pode ocorrer um abort, onde todas as alterações são descartadas.

Um sistema de transação deve controlar os acessos à memória, sendo armazenado, na transação, uma região na memória chamada write-set de forma a manter registro de todas as operações de escrita feitas na transação. Caso ocorra um commit, é realizada uma cópia da área para a memória global em tempo atômico, tipicamente escrevendo

(4)

este buffer na cache. Ocorrendo um abort, a operação é descartada. Leituras também precisam ter um controle sobre as operações feitas na transação, criando um read-set, uma transação que teve sucesso escreve à memória do read-set para os registradores.

A solução torna implícito qualquer tratamento relacionado à forma como as ope-rações serão resolvidas na memória, deve ser informado que uma nova transação ini-ciou com o comando TM_START(0, RW), indicando que uma nova transação começa, o RW sinaliza à transação que esta é uma transação com leitura e escrita, a instrução TM_LOAD(&counter) indica uma leitura da memória para uma variável local onde os dados serão manipulados e que através do TM_STORE(&counter, inc) o valor será arma-zenado na área write-set. A instrução TM_COMMIT tenta validar a transação.

3. Implementação do grafo não bloqueante

A utilização de variáveis atômicas e suas operações se deu, neste trabalho, com a uti-lização do padrão C++11 que provê acesso à biblioteca <atomic>, para compilar um código que utiliza bibliotecas do padrão C++11, a flag”-std=c++11” deve ser adicionada ao compilador. Cada instância e especialização do templatestd::atomic define um tipo atômico. Objetos do tipo atômico são os únicos livres de condição de corrida. Além das especializações, operações podem ser executadas por estes tipos atômicos.

A implementação do grafo utilizando a solução atômica tem como estrutura de dados uma matriz de inteiros atômicos que é a definição de arestas e um vetor de inteiros atômicos que são a representação dos vértices.

Entre as operações mencionadas na Seção 2.2, uma das mais importantes é a ope-raçãoatomic_compare_exchange, geralmente aplicada dentro de um laço de repetição em que o valor atômico é copiado para uma variável local, operações são aplicadas sobre esta variável, e então no final da operação um teste envolvendoatomic_compare_exchange é feito para atualizar o valor para a variável atômica.

A estrutura de grafo recebeu uma matriz de adjacências composta de inteiros atô-micos e um vetor de inteiros atôatô-micos representando os vértices do grafo.

4. Avaliação de Desempenho

Para comparar e analisar os efeitos positivos e negativos do uso de cada solução de con-trole a seção crítica aplicado em grafos, o experimento feito foi das seguintes operações básicas, inserção e remoção de um vértice, inserção e remoção de uma aresta e incremento e decremento do peso da aresta. O experimento utiliza um mesmo conjunto de dados de entrada, que correspondem às instruções a serem executadas sobre o grafo.

Os testes foram realizados em uma máquina com processador Bulldozer FX-8120 com 3.1 Ghz e oito cores físicos, memória RAM de 4GiB. Os testes foram executados trinta vezes para cada instância, as instâncias do experimento podem variar em número de thread e tamanho de grafo, com 2, 4, 6 e 8 para os números de thread e três tamanhos de grafo, 50, 200 e 400, onde foram feitas um conjunto com trinta milhões do operações. Os resultados são apresentados em função do tempo dado número de thread e tamanho do grafo e analisada a escalabilidade das soluções. A solução sequencial não será apresentada pois o problema é essencialmente concorrente logo a solução sequencial não é interessante.

(5)

A Figura 1(a) demonstra o comportamento das ferramentas de controle de memó-ria compartilhada aplicada em um grafo de tamanho 50, onde observa-se comportamentos diferentes entre as três ferramentas, variáveis atômicas conseguem um bom desempenho já com duas threads em comparação as outras soluções e que quanto maior for o nível de paralelismo maior o desempenho. Memórias transacionais alcançam um bom desem-penho até 4 threads, onde tem seu desemdesem-penho comprometido conforme acréscimo de novas threads.

A aplicação de mutex em grafo de tamanho 50 mostrou que o uso desta ferramenta em grafos de tamanho pequeno não apresenta bons resultados, mesmo apresentando locks finos, o desempenho não é considerado satisfatório em comparação as outras soluções. O desempenho com mutex melhora até 4 threads, mas o acréscimo do número de threads em um grafo pequeno gera uma possibilidade cada vez maior de ocorrer conflitos ao acessar o mesmo endereço, causando muitas trocas de contexto e diminuindo o desempenho.

O comportamento do experimento quando aplicado a um grafo de tamanho 200 pode ser analisado na figura 1(b),onde a curva que demonstra o desempenho com me-mórias transacionais (STM) apresenta uma curva mais suave, mutex agora apresentam ganhos significativos quando ocorre de desempenho, já que agora as novas threads reali-zam tarefas em simultâneo ao não incorrer em tantos conflitos de acesso a mesma seção crítica, uma vez que antes, a inclusão de novas threads tinha como resultado principal acrescentar overhead. O comportamento com operações atômicas não apresenta compor-tamento significativamente diferente em comparação ao exemplo anterior. Os resultados para grafo de tamanho 400 pode ser observado na Figura 1(c), a solução com mutex agora consegue ter bons ganhos de desempenho em comparação com os experimentos anterio-res, mesmo assim a solução com mutex só consegue ter um desempenho melhor quando comparamos 8 threads com mutex com a solução atômica com apenas 2 threads. A solu-ção STM consegue manter um desempenho estável a partir de 4 threads em execusolu-ção, a solução atômica apresenta o mesmo comparado aos outros experimentos.

Como é possível observar nos desempenhos apresentados, as ferramentas apresen-tam um comporapresen-tamento padrão com relação aos tempos de execução, com as operações em variáveis atômicas apresentando maior desempenho, seguindo de memórias transaci-onais em software e por último e pior desempenho mutex.

É possível notar também que as ferramentas possuem comportamentos diferentes quando aplicados a cada tamanho de grafo, o qual grafos de tamanho menor tenham uma chance maior de que operações sejam aplicadas a um mesmo endereço, causando assim conflitos no acesso.

5. Conclusão

Neste trabalho foi feita uma análise das principais soluções para controle em paralelo de memória compartilhada aplicada a estrutura de dados grafo. A implementação das bibliotecas em paralelo utilizando as ferramentas mutex, operações atômicas e memorias transacionais em software tem como objetivo poder mensurar o tempo que as ferramentas levam para solucionar tarefas específicas.

A solução utilizando mutex é a que possui pior desempenho na comparação com as outras ferramentas, aplicações que utilizam mutex em grafo não possuem bom desempe-nho em grafos de tamadesempe-nho pequeno, apresentando desempedesempe-nho pior conforme o número

(6)

(a) Operações sobre um grafo de tamanho 50 (b) Operações sobre um grafo de tamanho 200

(c) Operações sobre um grafo de tamanho 400

Figura 1. Operações aplicadas à grafos de tamanho 50, 200 e 400

de threads vai aumentando. O baixo desempenho em comparação as outras ferramentas abordadas somado com a dificuldade para programar utilizando mutex, principalmente quando deve-se utilizar mutex aninhados, tornam esta solução ultrapassada.

A solução com memórias transacionais em software, possui uma interface de pro-gramação com alto nível de abstração que tira do programador o controle de sincronização e deixando isto a cargo do sistema de execução. A biblioteca utilizada neste trabalho para utilizar memórias transacionais foi a tinySTM, esta mostra ter uma boa performance geral que, em alguns casos, se aproxima da implementação com operações atômicas.

A solução com operações atômicas foi implementada com recursos de C++11. Nos experimentos foi mostrado que para conseguir o melhor desempenho utilizando grafo em paralelo a solução atômica é a indicada, com desempenho de até 10 vezes superior em alguns casos quando comparado à solução com mutex. O desempenho alto das opera-ções atômica se da em função dos mecanismos de controle de baixo nível, que dão ao programador controle para extrair o máximo de desempenho. Soluções atômicas trazem bom desempenho quando a aplicação é especializada, para soluções genéricas, uma outra ferramenta deve ser utilizada.

Referências

Farook, M. and Graham, P. (1998). Managing long linked lists using lock-free techniques. In High Performance Computing Systems and Applications, pages 407–422. Springer. Herlihy, M. and Moss, J. E. B. (1993). Transactional memory: Architectural support for

lock-free data structures.

Shavit, N. and Touitou, D. (1997). Software transactional memory. Distributed Compu-ting, 10(2):99–116.

Referências

Documentos relacionados

No entanto, maiores lucros com publicidade e um crescimento no uso da plataforma em smartphones e tablets não serão suficientes para o mercado se a maior rede social do mundo

O valor da reputação dos pseudônimos é igual a 0,8 devido aos fal- sos positivos do mecanismo auxiliar, que acabam por fazer com que a reputação mesmo dos usuários que enviam

Entre as atividades, parte dos alunos é também conduzida a concertos entoados pela Orquestra Sinfônica de Santo André e OSESP (Orquestra Sinfônica do Estado de São

Para isso, procurou-se fazer: recupera- ção histórica da ocupação do bairro e formas de organização da população local; revisão da literatura sobre economia solidária

O objetivo principal deste estudo de caso era verificar o grau de valorização da medicina popular de plantas entre o próprio povo, tendo por base uma comunidade com diferentes

Mais ainda, disponibiliza um conjunto de recomendações destinadas aos governos e às entidades doadoras, juntamente com entidades parceiras, para assegurar que todas as crianças

Nessa situação temos claramente a relação de tecnovívio apresentado por Dubatti (2012) operando, visto que nessa experiência ambos os atores tra- çam um diálogo que não se dá

O score de Framingham que estima o risco absoluto de um indivíduo desenvolver em dez anos DAC primária, clinicamente manifesta, utiliza variáveis clínicas e laboratoriais