• Nenhum resultado encontrado

4.1 AMBIENTE EXPERIMENTAL

4.1.2 As Máquinas Hospedeiras

Para acomodar a placa H.O.T. II com o projeto do hardware do Sincronizador de Topo, foi utilizada uma máquina com processador Intel modelo Celeron 766 MHz, 128 MB de memória RAM e 10 GB de disco rígido, executando sistema operacional Windows 98 Segunda Edição, no idioma português. Os Sincronizadores não necessitam comunicação com o sistema operacional do usuário da rede de sincronização. Assim, a máquina com o Sincronizador de Topo roda o sistema Windows apenas por conveniência. Na prática, essa máquina funciona apenas como uma fonte de alimentação para a placa H.O.T. II, pois, após a carga do hardware do Sincronizador na FPGA, nenhuma operação de escrita ou leitura no PCI é necessária e nem mesmo possível de ser realizada nesse hardware (ele não inclui necessariamente a interface PCI). Na verdade, poderíamos ter gravado o projeto do Sincronizador em uma memória flash presente na placa H.O.T. II, o que dispensaria qualquer

ação extra para o uso do hardware projetado, mas, por comodidade, isso não foi feito. A interface de software utilizada para a comunicação com a placa H.O.T. II e carga do

hardware do Sincronizador no ambiente Windows foi o TestCommander [VCC99b], que é

uma ferramenta de apoio fornecida junto com a placa H.O.T. II.

Os Nós foram instalados em duas máquinas com processador AMD modelo Athlon XP 1800+, 256 MB de memória RAM e 20 GB de disco rígido, executando sistema operacional Linux distribuição Red Hat 7.1, Kernel 2.4.20-18.7. A interface de software utilizada para a comunicação com a placa H.O.T. II e carga do hardware do Nó na FPGA foi o módulo desenvolvido e cedido pela equipe de pesquisadores do Departamento de Informática da UFES e da COPPE/UFRJ, descrito na Subseção 3.4.5 . Instalamos nessas máquinas, além das placas H.O.T. II, placas de interface de rede Ethernet 100Base-TX que, devidamente conectadas a duas portas de um switch de mesma tecnologia, interligavam as estações do nosso cluster experimental, implementando a rede de comunicação de dados usada nos experimentos.

4.1.3 Programas de Teste

Consideramos o desenvolvimento de programas de aplicação reais que façam uso de nossa rede de sincronização fora do escopo deste trabalho de pesquisa. Assim, para nossos experimentos de validação do hardware desenvolvido e avaliação da rede de sincronização, implementamos pequenos programas em linguagem C que exercitam características chave da rede de sincronização. Esses programas não proporcionam computação produtiva, porém, nos permitem obter dados de desempenho do sistema quando utilizando as primitivas implementadas por hardware, mediante o uso da rede auxiliar de sincronização e, também, as mesmas primitivas implementas por software, mediante o uso da rede de dados.

Desenvolvemos um programa para executar uma computação paralela que privilegia o uso de sincronização por barreiras, tendo como suporte o Message Passing Interface – MPI [ARPA95]. A Figura 4.3 (página 65) contém o código deste programa, que implementa barreira da forma convencional, isto é, por software. Cada Nó processador implementa a barreira MPI_Barrier(MPI_COMM_WORLD) utilizando a rede de comunicação de dados Ethernet 100Base-TX que interliga esses Nós. A seta indica a linha onde a barreira é efetivamente implementada.

O programa implementa um número múltiplo de 256 barreiras a cada execução. Este número foi escolhido para permitir que todas as 256 barreiras disponíveis fossem testadas na implementação de barreira por hardware (Figura 4.4, página 66). Antes do trecho de código de interesse, uma barreira extra, por software, é implementada para permitir que este trecho seja iniciado simultaneamente em todos os Nós processadores. Após esse ponto, foi introduzido um atraso (for (i=0;i<1500;i++)) para simulação de um possível processamento útil antes da implementação da barreira propriamente dita. Como o programa foi compilado com o nível padrão de otimização (nível 0 – sem otimização), o compilador não otimiza o código e o atraso é implementado. O restante do código é destinado à instrumentação, como será explicado adiante nesta subseção.

int main(int argc, char *argv[]) {

unsigned long long start, stop, start_all, stop_all; unsigned int m=0xC0, b=0, i=0, j=0, k=0; double tmp=0.0, t_all=0.0, acc=0.0, H_acc=0.0; double media=0.0, H_media=0.0, maior=0.0, menor=100.0; FILE *arq;

char fname[64]; int my_rank, num_procs; j = atoi(arbv[1]); MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Barrier(MPI_COMM_WORLD);

start_all = SyncGetTsc(); //rdtsc no inicio do processamento for (k=0;k<j;k++) { for (b=0;b<256;b++) { for (i=0;i<1500;i++) { }

SyncGetTsc(); // rdtsc na barreira local atingida

MPI_Barrier(MPI_COMM_WORLD);

SyncGetTsc(); //rdtsc na barreira atingida tmp = ( (double)(stop – start) / (double)1532939000; acc += tmp; H_acc += ((double)1)/tmp; if (maior < tmp) maior=tmp; if (menor > tmp menor=tmp; } }

media = acc / (double)(j*256); H_media = (double)(j*256) / H_acc;

stop_all = SyncGetTsc() // rdtsc no final do processamento sprintf(fname,"medicoes_mpi_sw%d.txt",j);

arq = fopen (fname,"w");

fprintf (arq,“ Iteracoes: %d

Tempo total de processamento: %.9f Tempo medio aritmetico da barreira: %.9f seg Tempo medio harmonico da barreira: %.9f seg. Tempo da maior barreira: %.9f seg.

Tempo da menor barreira: %.9f seg.\n”, j,t_all, media, H_media, maior, menor); fclose(arq);

MPI_Finalize(); }

int main(int argc, char *argv[]) {

unsigned long long start, stop, start_all, stop_all; unsigned int m=0xC0, b=0, i=0, j=0, k=0; double tmp=0.0, t_all=0.0, acc=0.0, H_acc=0.0; double media=0.0, H_media=0.0, maior=0.0, menor=100.0; FILE *arq;

char fname[64]; int my_rank, num_procs; j = atoi(arbv[1]); MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Barrier(MPI_COMM_WORLD);

start_all = SyncGetTsc(); //rdtsc no inicio do processamento for (k=0;k<j;k++) { for (b=0;b<256;b++) { for (i=0;i<1500;i++) { }

SyncGetTsc(); // rdtsc na barreira local atingida

MPI_Barrier(MPI_COMM_WORLD);

SyncGetTsc(); //rdtsc na barreira atingida tmp = ( (double)(stop – start) / (double)1532939000; acc += tmp; H_acc += ((double)1)/tmp; if (maior < tmp) maior=tmp; if (menor > tmp menor=tmp; } }

media = acc / (double)(j*256); H_media = (double)(j*256) / H_acc;

stop_all = SyncGetTsc() // rdtsc no final do processamento sprintf(fname,"medicoes_mpi_sw%d.txt",j);

arq = fopen (fname,"w");

fprintf (arq,“ Iteracoes: %d

Tempo total de processamento: %.9f Tempo medio aritmetico da barreira: %.9f seg Tempo medio harmonico da barreira: %.9f seg. Tempo da maior barreira: %.9f seg.

Tempo da menor barreira: %.9f seg.\n”, j,t_all, media, H_media, maior, menor); fclose(arq);

MPI_Finalize(); }

Com uma pequena adequação no código da Figura 4.3, criamos uma versão que implementa a barreira por hardware, usando os módulos de software descritos no capítulo anterior (Subseção 3.4.5 ) e fazendo, assim, uso da rede auxiliar de sincronização como suporte para esse propósito. A Figura 4.4 ilustra o código modificado. A linha de código de chamada da barreira por software foi retirada. As setas indicam as linhas de chamada de função que foram incluídas para que a barreira por hardware fosse implementada. Nota-se que essa implementação exige que três chamadas de função sejam feitas. A primeira delas faz a criação da barreira, a segunda informa que a barreira foi atingida no Nó de processamento atual e a terceira verifica, através de espera ocupada, se a barreira foi atingida por todos os Nós processadores participantes dela.

int main(int argc, char *argv[]) {

unsigned long long start, stop, start_all, stop_all; unsigned int m=0xC0, b=0, i=0, j=0, k=0; double tmp=0.0, t_all=0, acc=0.0, H_acc=0.0;

double media=0.0,H_media=0.0, maior=0.0, menor=100.0; FILE *arq;

char fname[64]; int my_rank, num_procs; SyncOpenBoard(); j = atoi(arbv[1]); MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Barrier(MPI_COMM_WORLD);

start_all = SyncGetTsc(); //rdtsc no início do processamento for (k=0;k<j;k++) { for (b=0;b<256;b++) { SyncCriaBarreira(b,m); for (i=0;i<1500;i++) { }

start = SyncGetTsc(); //rdtsc na barreira local atingida

SyncAtingeBarreira(b); SyncVerificaBarreira(b);

stop = SyncGetTsc(); //rdtsc na barreira atingida tmp = ( (double)(stop – start) / (double)1532939000; acc +=tmp; `H_acc += ((double)1) / tmp; if (maior < tmp) maior = tmp; if (menor > tmp) menor = tmp; } }

media = acc / (double)(j*256); H_media = (double)(j*256) / H_acc;

stop_all = SyncGetTsc(); // rdtsc no final do processamento t_all = ((double)(stop_all – start_all) / (double)1532939000)); sprintf(fname,"medicoes_mpi_hw%d.txt",j);

arq = fopen (fname,"w");

fprintf (arq,“ Iteracoes: %d

Tempo total de processamento: %.9f Tempo medio aritmetico da barreira: %.9f seg Tempo medio harmonico da barreira: %.9f seg. Tempo da maior barreira: %.9f seg.

Tempo da menor barreira: %.9f seg.\n”, j,t_all, media, H_media, maior, menor); fclose(arq);

SyncCloseBoard(); MPI_Finalize(); }

int main(int argc, char *argv[]) {

unsigned long long start, stop, start_all, stop_all; unsigned int m=0xC0, b=0, i=0, j=0, k=0; double tmp=0.0, t_all=0, acc=0.0, H_acc=0.0;

double media=0.0,H_media=0.0, maior=0.0, menor=100.0; FILE *arq;

char fname[64]; int my_rank, num_procs; SyncOpenBoard(); j = atoi(arbv[1]); MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Barrier(MPI_COMM_WORLD);

start_all = SyncGetTsc(); //rdtsc no início do processamento for (k=0;k<j;k++) { for (b=0;b<256;b++) { SyncCriaBarreira(b,m); for (i=0;i<1500;i++) { }

start = SyncGetTsc(); //rdtsc na barreira local atingida

SyncAtingeBarreira(b); SyncVerificaBarreira(b);

stop = SyncGetTsc(); //rdtsc na barreira atingida tmp = ( (double)(stop – start) / (double)1532939000; acc +=tmp; `H_acc += ((double)1) / tmp; if (maior < tmp) maior = tmp; if (menor > tmp) menor = tmp; } }

media = acc / (double)(j*256); H_media = (double)(j*256) / H_acc;

stop_all = SyncGetTsc(); // rdtsc no final do processamento t_all = ((double)(stop_all – start_all) / (double)1532939000)); sprintf(fname,"medicoes_mpi_hw%d.txt",j);

arq = fopen (fname,"w");

fprintf (arq,“ Iteracoes: %d

Tempo total de processamento: %.9f Tempo medio aritmetico da barreira: %.9f seg Tempo medio harmonico da barreira: %.9f seg. Tempo da maior barreira: %.9f seg.

Tempo da menor barreira: %.9f seg.\n”, j,t_all, media, H_media, maior, menor); fclose(arq);

SyncCloseBoard(); MPI_Finalize(); }

Para a validação e avaliação experimental do lock por hardware desenvolvemos um código similar aos desenvolvidos para a implementação de barreiras. Esse código inclui um trecho de computação onde é simulada a necessidade de uma exclusão mútua, implementada com o uso da rede auxiliar de sincronização. A Figura 4.5 (página 68) mostra o código desenvolvido. Como pode ser observado, a implementação do lock é um pouco diferente da implementação de barreiras. Antes que um lock seja solicitado, é necessário verificar se o mesmo está concedido naquele momento. Essa verificação é feita por meio da função SyncVerificaLock(lock), passando-se o lock (lock) solicitado como parâmetro. Caso ele esteja concedido naquele momento, o programa permanece em espera ocupada até que o lock seja liberado. Nesse instante, uma solicitação é disparada através da chamada de função SyncGetLock(lock,proc), passando-se o lock (lock) solicitado e a identificação do processador local (proc) como parâmetros. Se o lock for concedido a outro processador que não o local, o programa fica em espera ocupada, efetuando solicitações sucessivas, até que receba a concessão para o processador local (proc). Ao receber a concessão do lock (lock), a exclusão mútua está garantida e o programa pode executar o trecho em questão, liberando, posteriormente, o lock (lock) por meio da chamada de função SyncFreeLock(lock).

Infelizmente, não obtivemos acesso a programas de teste que implementassem lock por

software e, portanto, não tivemos como utilizá-los em nossos experimentos.

Os programas foram instrumentados para medir os tempos de barreira ou de lock. Para realizar a instrumentação do código, utilizamos a instrução read-time stamp counter – rdtsc –,

opcode 0F 31 [Intel98]. Essa instrução retorna o conteúdo de um contador de 64 bits que é

incrementado pelo processador a cada ciclo de relógio. Para computar tempo, efetua-se a diferença entre duas leituras desse contador feitas dentro de um mesmo programa e, em seguida, dividi-se o resultado pela freqüência de trabalho do processador em GHz (gigahertz). Deste modo, obtivemos a duração em ns (nanosegundos) do evento contido entre as duas leituras realizadas.

Para facilitar o uso da instrução rdtsc nos programas de teste, desenvolvemos a função SyncGetTsc(), que retorna os 64 bits organizados para que as operações aritméticas relativas aos cálculos de tempos possam ser efetuadas. Assim, para medir a duração de uma barreira são feitas duas chamadas dessa função, uma quando a barreira é atingida no Nó processador

local e outra quando ela é liberada, ou seja, quando é atingida por todos os outros Nós que participam dela.

int main(int argc, char *argv[]) {

unsigned long long start, stop, start_all, stop_all; unsigned int b=0, i=0, j=0, k=0, l=0, lock=0, proc; double tmp=0.0, t_all=0, acc=0.0, H_acc=0.0;

double media=0.0,H_media=0.0, maior=0.0, menor=100.0; FILE *arq;

char fname[64]; int my_rank, num_procs; SyncOpenBoard(); j = atoi(arbv[1]); proc = my_rank +1;

SyncFreeLock(lock); // limpa a memoria MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Barrier(MPI_COMM_WORLD);

start_all = SyncGetTsc(); //rdtsc no início do processamento for (k=0;k<j;k++)

{

l=SyncVerificaLock(lock); do

{

start = SyncGetTsc(); //solicitacao do lock if (l == 0)

SyncGetLock(lock,proc);

l=SyncVerificaLock(lock); } while (l != proc);

stop = SyncGetTsc(); //concessao do lock for (i=0;i<1500;i++)

{ }

SyncFreeLock(lock) // liberacao do lock;

tmp = ( (double)(stop – start) / (double)1532939000; acc +=tmp; `H_acc += ((double)1) / tmp; if (maior < tmp) maior = tmp; if (menor > tmp) menor = tmp; }

media = acc / (double)j; H_media = (double)j / H_acc;

stop_all = SyncGetTsc(); // rdtsc no final do processamento t_all = ((double)(stop_all – start_all) / (double)1532939000)); sprintf(fname,"medicoes_mpi_hw_lock%d.txt",j); arq = fopen (fname,"w");

fprintf (arq,“ Iteracoes: %d

Tempo total de processamento: %.9f Tempo medio aritmetico do lock: %.9f seg Tempo medio harmonico do lock: %.9f seg.

Tempo do maior lock: %.9f seg.

Tempo do menor lock: %.9f seg.\n”, j,t_all, media, H_media, maior, menor); fclose(arq);

SyncCloseBoard(); MPI_Finalize(); }

int main(int argc, char *argv[]) {

unsigned long long start, stop, start_all, stop_all; unsigned int b=0, i=0, j=0, k=0, l=0, lock=0, proc; double tmp=0.0, t_all=0, acc=0.0, H_acc=0.0;

double media=0.0,H_media=0.0, maior=0.0, menor=100.0; FILE *arq;

char fname[64]; int my_rank, num_procs; SyncOpenBoard(); j = atoi(arbv[1]); proc = my_rank +1;

SyncFreeLock(lock); // limpa a memoria MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Barrier(MPI_COMM_WORLD);

start_all = SyncGetTsc(); //rdtsc no início do processamento for (k=0;k<j;k++)

{

l=SyncVerificaLock(lock); do

{

start = SyncGetTsc(); //solicitacao do lock if (l == 0)

SyncGetLock(lock,proc);

l=SyncVerificaLock(lock); } while (l != proc);

stop = SyncGetTsc(); //concessao do lock for (i=0;i<1500;i++)

{ }

SyncFreeLock(lock) // liberacao do lock;

tmp = ( (double)(stop – start) / (double)1532939000; acc +=tmp; `H_acc += ((double)1) / tmp; if (maior < tmp) maior = tmp; if (menor > tmp) menor = tmp; }

media = acc / (double)j; H_media = (double)j / H_acc;

stop_all = SyncGetTsc(); // rdtsc no final do processamento t_all = ((double)(stop_all – start_all) / (double)1532939000)); sprintf(fname,"medicoes_mpi_hw_lock%d.txt",j); arq = fopen (fname,"w");

fprintf (arq,“ Iteracoes: %d

Tempo total de processamento: %.9f Tempo medio aritmetico do lock: %.9f seg Tempo medio harmonico do lock: %.9f seg.

Tempo do maior lock: %.9f seg.

Tempo do menor lock: %.9f seg.\n”, j,t_all, media, H_media, maior, menor); fclose(arq);

SyncCloseBoard(); MPI_Finalize(); }

Figura 4.5: Trecho de código com lock por hardware.

Para medir o tempo de duração de um lock, fazemos também duas chamadas da função SyncGetTsc(), sendo uma imediatamente antes da solicitação do lock para a entrada na região crítica do programa e outra imediatamente após a concessão do lock ser feita para o

processador em questão, possibilitando que o trecho do programa que contém a região crítica seja executado. Durante a execução do programa são realizadas as operações de cálculo para a determinação dos tempos de nosso interesse. Ao final da computação, são fornecidos o tempo de execução do programa, o tempo máximo e mínimo de duração de uma barreira ou lock e as médias aritmética e harmônica da duração das barreiras ou locks.

Esses programas foram compilados com o compilador do MPI – mpicc – e, assim sendo, são executados nos dois microcomputadores configurados como Nós processadores com o uso de chamadas da biblioteca LAM-MPI [LAM-MPI03]. Nós utilizamos o LAM-MPI versão 6.5.9.

4.1.4 Métricas

As métricas que utilizamos para a avaliação experimental foram a média harmônica dos tempos das barreiras ou locks, o tempo total de execução dos programas de teste e o speedup apresentado pelas implementações de barreira por hardware em relação às implementações por software.

O tempo de duração de uma barreira foi obtido a partir de duas leituras feitas com a instrução rdtsc em pontos pré-definidos do programa. Como pode ser visto na Figura 4.3 (página 65) e na Figura 4.4 (página 66), a primeira leitura é efetuada imediatamente antes da barreira ser atingida pelo Nó processador local e a segunda é realizada imediatamente após a barreira ter sido atingida por todos os Nós processadores que participam dela.

O tempo de duração de um lock é obtido de maneira similar, com a primeira leitura sendo efetuada imediatamente antes da solicitação de concessão do lock e a segunda leitura sendo realizada imediatamente após a concessão do lock para o Nó processador local, conforme a Figura 4.5 (página 68).

O tempo total de execução do programa é conseguido da mesma maneira. Uma leitura com a instrução rtdsc é feita imediatamente após a declaração das variáveis e a outra é feita imediatamente após o final da computação.

De posse dessas medidas, o cálculo de cada um desses tempos é feito através da fórmula:

fp

t

t

onde

tempo

total é o tempo que se deseja conhecer,

t

inicial e tfinal são, respectivamente, a primeira e a segunda leituras efetuadas com a instrução rdtsc e fp é a freqüência de trabalho do processador do Nó. O valor encontrado,

tempo

total, é o tempo, em segundos, decorrido entre as duas leituras da instrução rdtsc.

Utilizamos a média harmônica com a intenção de sumarizar os valores obtidos através de um único número que representasse o desempenho do sistema. Para se calcular a média harmônica dos tempos de duração de uma barreira ou de um lock, utilizamos a fórmula:

= =

=

i n i

ti

n

medhar

1

1

(4.2)

onde

medhar

é a média harmônica dos tempos de duração de uma barreira ou de um lock,

n

é o número total de barreiras ou de locks executados no programa de teste e

ti

são os tempos de barreiras ou de locks medidos durante a execução do programa.

Utilizamos a média harmônica ao invés da média aritmética com o objetivo de reduzir o efeito de tempos de barreiras elevados que possam ocorrer em decorrência de chamadas de sistemas durante a execução do programa de teste sobre a média geral dos tempos de barreiras. A média harmônica tem a característica de ser menos influenciada por picos.

O speedup é uma unidade adimensional que indica quantas vezes um sistema é mais rápido que outro. Neste trabalho, o speedup é calculado através da fórmula:

hard soft

t

t

speedup

=

(4.3)

onde

t

soft é o tempo da implementação por software e

t

hard é o tempo da implementação por

barreiras e locks implementados por hardware frente a barreiras implementadas por software. A avaliação do mecanismo de broadcast implementado por nós foi deixada para trabalhos futuros.

5.1 TEMPO DE BARREIRA

Nesta seção apresentamos os experimentos realizados para a comparação de desempenho entre as implementações de barreira por software, com a utilização da biblioteca MPI, e por

hardware, com a utilização da rede auxiliar de sincronização desenvolvida. Para isso, fizemos

uma bateria de testes com o programa que implementa barreiras por software, apresentado na Figura 4.3 (página 65), e outra com o programa que implementa barreira por hardware, apresentado na Figura 4.4 (página 66). Em cada bateria, executamos o programa por seis vezes, variando exponencialmente, em potências de dez, o número de barreiras implementadas. A cada execução, realizamos as medições do tempo de cada barreira e do tempo total de execução de cada programa. A Tabela 5.1 mostra os resultados atingidos com o programa que implementa barreiras por hardware e a Tabela 5.2 (página 72) mostra os resultados atingidos com o programa que implementa barreiras por software. Como pode ser notado, os resultados obtidos para os tempos médios da barreira implementada por hardware são menores que os resultados obtidos para os tempos médios da barreira implementada por

software. Nota-se também que o tempo total de execução dos programas apresenta uma

diferença considerável, com nítida vantagem de desempenho para a implementação em

hardware. Tendo em vista que o clock real dos processadores dos Nós utilizados nos

experimentos é de 1.532.939.000Hz, a precisão nos tempos medidos é da ordem de sub- nanosegundos.

Tabela 5.1: Implementação de barreiras por hardware. Quantidade de barreiras (un) Média harmônica (µs) Tempo de execução do programa (s) 256 1,713 0,001969581 2.560 1,906 0,019094939 25.600 1,656 0,191182427 256.000 2,059 1.942289606 2.560.000 1,763 19,861682971 25.600.000 1,852 199,708281373

Tabela 5.2: Implementação de barreiras por software. Quantidade de barreiras (un) Média harmônica (µs) Tempo de execução do programa (s) 256 106,064 0,031130420 2.560 95,284 0,294931485 25.600 87,773 2,678029310 256.000 88,905 27,129206158 2.560.000 89,008 271,460952398 25.600.000 87,074 2.658,359010029

O gráfico da Figura 5.1 mostra a comparação de desempenho entre as duas implementações de barreira, isto é, por hardware e por software. O eixo horizontal contém o número de barreiras implementadas durante cada execução do programa de testes. O eixo vertical mostra o quanto a barreira por hardware foi mais rápida que a barreira por software, em média. Os valores que aparecem no eixo vertical são o resultado da divisão da coluna “Média harmônica” da Tabela 5.2 (tempo médio de barreira por software) pela mesma coluna da Tabela 5.1 (tempo médio de barreira por hardware). Esses valores mostram que a barreira implementada por hardware é, em média, cerca de 50 vezes mais rápida que a barreira implementada por software.

61,9 2 49,9 9 53,0 0 43,1 8 50,4 9 47,0 2 30 35 40 45 50 55 60 65 256 2560 2560 0 2560 00 2560 000 2560 0000 Núm ero de barreiras s o ft w a re /h a rd w a re

Figura 5.1: Comparação entre os tempos médios de execução de barreiras implementadas por hardware e por software em função do número de barreiras implementadas.

O gráfico da Figura 5.2 mostra o speedup conseguido no tempo de execução do programa de teste na implementação por hardware frente a implementação por software. Como pode ser observado, o programa com implementação de barreira por hardware apresentou um tempo de execução da ordem de 13 vezes menor que o programa com a implementação por software, quando o número de barreiras ultrapassa a fronteira das 256 mil. Quando o número de barreiras implementadas é baixo, na casa de poucas centenas, o programa com a implementação de barreira por hardware se mostrou cerca de 16 vezes mais rápido em sua execução. 16,5 1 14,8 3 13,9 1 13,3 1 13,4 0 13,2 8 10 11 12 13 14 15 16 17 256 2560 2560 0 2560 00 2560 000 2560 0000 Núm ero de barreiras s o ft w a re /h a rd w a re

Figura 5.2: Speedup no tempo total de execução do programa de teste em função do número de barreiras implementadas.

5.2 TEMPO DE LOCK

Nesta seção apresentamos os experimentos realizados para medir os tempos de duração de

locks implementados por hardware. Para isso, fizemos uma bateria de testes onde executamos

o programa apresentado na Figura 4.5 (página 68) por seis vezes, variando exponencialmente, em potências de dez, o número de locks implementados, seguindo o mesmo modelo utilizado nos testes com barreiras. A cada execução, realizamos as medições do tempo de cada lock e do tempo total de execução de cada programa. A Tabela 5.3 mostra os resultados obtidos nos experimentos. Como pode ser verificado, os resultados encontrados para os tempos médios dos locks implementados por hardware são muito próximos aos encontrados para os tempos médios das barreiras implementadas por hardware. Essa proximidade nos resultados era

esperada, tendo em vista que as operações de tratamento e transmissão das mensagens de barreiras e locks pela rede auxiliar de sincronização são similares.

Tabela 5.3: Implementação de lock por hardware.

Documentos relacionados