SISTEMAS OPERACIONAIS
ROGER ROBERTO ROCHA DUARTE – GRR 20084281
THIAGO HENRIQUE DOS SANTOS PICHARSKI – GRR2008
Introdução
Visando buscar sempre o máximo de desempenho, tanto na resolução do problema em si, mas
também para o bem uso de memória e CPU, o problema foi pensado várias vezes de forma que, a
execução fosse o mais rápido possível buscando a utilização mínima memória e a máxima dos
processadores.
Em si, o sistema foi montado para sempre trabalhar, ou seja, ficar parado o mínimo possível diante
das contagens de distâncias repetidas, ou do acesso do buffer global do sistema.
Arquitetura do Sistema
Como bem dito na introdução, o sistema foi elaborado visando a velocidade, estabilidade e
consumo adequado de memória.
Levando em conta esses conceitos, o sistema foi elaborado da seguinte forma:
Cálculos:
– Para efetuar os diversos cálculos, o modelo mais simples foi escolhido, ou seja,
colocamos as N Threads em ordem, logo a thread 0 faz a contagem de 0 até 1,2,3...m; a
thread 1 faz a contagem de 1 até 2, 3, 4...m, e assim por diante. Quando a thread 0
completa a contagem, esta pula para a linha N (0 +N), a thread 1 pula para a linha N+1,
e assim por diante.
– Essas threads que fazem os cálculos são chamadas de threads contadoras.
Gerência de Threads:
– Para cada thread contadora, existe uma outra thread, com a função de contar as
distâncias repetidas e colocar no buffer.
Para 4 Threads Contadoras:
Thread Wait
Thread Wait
Thread Wait
Thread Wait
Thread Contadora 0 Thread Contadora 1 Thread Contadora 2 Thread Contadora 3
O exemplo acima, mostra o detalhamento das gerências de Threads.
Memória:
– Na elaboração do sistema, procuramos dispersar bem memória cedida pela máquina.
– O sistema possui uma estrutura global para armazenar os dados sumarizados, ou seja,
com as distâncias e com números que cada Thread calculou.
– Cada Thread contadora possui N pequenos buffers. Quando um enche a thread passa
automaticamente a usar o outro buffer. Nesse mesmo instante a thread contadora dona
desse buffer avisa a sua Thread Wait que este buffer está cheio, então a Thread Wait
calcula as distâncias repetidas e, assim que possível, coloca esse resultado na estrutura
de sumarizada.
Gerencia de Memória:
– Para os buffer de cada Thread foi utilizado uma tabela Hash.
– A escolha desse modelo, foi pensando no número de buscas e inserções que o sistema
teria que fazer no futuro. E como, busca e inserção podem ser O(1) – quando não
ocorrem colisões ou algo do gênero, Hash foi uma estrutura viável. No começo foi
pensado na utilização de uma árvore B e de uma árvore AVL (ou rubro-negra), porém,
com o uso de ponto flutuante, ficou inviável a utilização dessas estruturas – já que essas
utilizam muitas comparações, operações nas quais poderiam acarretar em erros.
Evolução do Sistema:
– A primeira versão elabora foi seqüencial. A estrutura contida era uma lista sequencial,
para uma entrada de 5000 elementos, o sistema demora cerca de 15 ~20 segundos para
execução – o que na última versão é praticamente instantâneo.
– A segunda versão, foi elaborado um sistema com N Thread e com um buffer global para
o resultado. Cada thread tinha um pequeno buffer, porém quando esse se enchia, a
própria thread tinha que elaborar os cálculos de repetidos e etc – deixando assim, parte
do sistema parado.
– Com o passar do tempo, a segunda versão foi sem melhorada, até que chegarmos na
arquitetura final.
Experimentos
Estruturas:
– Foi testadas várias estruturas que seriam utilizadas para armazenar em memória os
buffers das threads e resultado sumarizado das mesmas. Numa primeira idéia foi testada
uma árvore AVL, a qual não foi viável devido ao número de inserções seqüenciais
causar um grande overhead nas rotações da árvore.
– Em seguida pensamos no uso da árvore B que é uma estrutura muito robusta, mas usa-la
não era justificável ao comparar sua complexidade à simplicidade dos dados que seriam
armazenados.
– Em fim foi decidido usar tabela de dispersão que resolve facilmente o problema com
pontos flutuantes e tem um custo de inserção e busca extremamente rápido. O grande
problema do uso da tabela de dispersão é quando ocorre muitas colisões, pensando nisso
foi adotada uma função de dispersão com uma certa complexidade, a divisão de
polinômios CRC-32, que diminui consideravelmente as colisões, usamos arvores AVL
no lugar de listas em nas linhas da tabela para diminuir o custo de busca, em caso de
colisões.
Threads:
– Em uma primeira implementação foi usado 4 threads sumarizando as linhas múltiplas do
seu próprio ID, essas mesmas threads escreviam na estrutura principal com uso de
semáforo para manter a consistência, essa versão não era eficiente devido as threads
terem que parar para inserirem na estrutura causando um enorme gargalo no fluxo de
execução.
– Em uma segunda ideia foi adicionado um buffer em cada thread que era alimentado
pelas threads já citadas acima e um pool de threads consumindo o buffer e sumarizando
na estrutura principal, usando os semáforos da mesma forma. Melhorou
significativamente o desempenho, mas ainda sim as threads produtoras ficavam
aguardando por ter enchido o buffer e a thread consumidora não ter conseguido
consumir o buferr por estar esperando o semáforo.
– Para minimizar o problema anterior foi usado vários níveis de buffers, assim quando o
um buffer enche as threads continuam escrevendo nos buffers seguintes equanto os
anteriores esperam para serem consumidos, retornando a usar os anteriores assim que
stes estiverem vazios. Isto melhorou o fluxo do programa e mantem as threads
funcionando quase que em tempo integral.
Conclusão
Com a evolução para a programação paralela, podemos sentir a real diferença na resolução
do problema. Nos testes, a mudança de 4 para 6 e posteriormente para 8 threads, verificamos uma
mudança realmente significativa no tempo de execução.
O conjunto de estruturas de dados escolhidas também causaram enorme impacto no
desempenho do programa. O gargalo do programa está localizado na estrutura compartilhada que
todas as threads escrevem, sendo necessária a proteção com semáforos para manter a consistência,
com isto foi primordial o uso de baixo custo, analisando casos gerais de execução, o melhor
equilíbrio entre os experimentos já citados foi tabela de dispersão + árvore AVL.
Download

SISTEMAS OPERACIONAIS ROGER ROBERTO ROCHA DUARTE – GRR 20084281