• Nenhum resultado encontrado

Quadratura Adaptativa José Eduardo Talavera Herrera

N/A
N/A
Protected

Academic year: 2021

Share "Quadratura Adaptativa José Eduardo Talavera Herrera"

Copied!
10
0
0

Texto

(1)

Quadratura Adaptativa

José Eduardo Talavera Herrera

jherrera@inf.puc-rio.br

1. Introdução

Este trabalho apresenta algumas variantes do algoritmo da quadratura adaptativa [1], utilizando pthreads e OpenMP. Não seção 2. É apresentado o conceito da quadratura adaptativa. O problema que se pretende resolver neste trabalho é descrito na seção 3. Uma solução sequencial para a quadratura adaptativa é definida na seção 4. A seção 5 e 6 são usadas para desenvolver a quadratura adaptativa usando pThread e OpenMP. Por último os resultados são apresentados não seção 7.

2. Quadratura Adaptativa

Considere uma função f(x) continua e não negativa, e também dois valores l e r, onde l < r. Ver Figura 1 . Uma maneira de aproximar o valor da área embaixo da curva é dividir o intervalo [l,r] em uma serie de subintervalos, e usar trapézios para aproximar a área de cada subintervalo. Por exemplo, para aproximar da área de uma função f no intervalo [a,b], primeiro obter o valor da base do trapézio b-a , depois obter o tamanho dos lados f(a) e f(b) e usar a formula clássica:

Area = (f(a) + f(b)) * (b-a) *0.5

O problema da quadratura pode ser resolvido estaticamente o dinamicamente. A abordagem estática usa um número fixo de intervalos de igual tamanho, computa a área do trapézio para cada intervalo e suma os resultados. O processo é repetido para cada intervalo, normalmente cada um é dividido em dois novos subintervalos, acabando quando as aproximações das áreas são próximas a um valor aceitável. A abordagem dinâmica começa com um intervalo [l,r] e é computado o ponto médio entre eles m. Depois ela computa a área de três trapézios: A1) a primeira limitada por l, r, f(l), f(r) que é a área de maior tamanho embaixo a função; A2) a segunda limitada por l, m, f(l), f(m); e A3) m, r, f(m), f(r). Ver Figura 1. Continuando com o processo, a abordagem dinâmica compara a área de maior tamanho com a suma das áreas menores. Se estas áreas são muito próximas, a abordagem considera a suma de estas últimas áreas como uma aproximação aceitável da área embaixo de f . Em caso contrario, o problema é repetido para resolver os dois subproblemas gerados, ou seja, computar a área dos intervalos [l,m] e [m,r]. Este processo é repetido recursivamente até a solução de cada problema ser aceitável. No final do processo, a abordagem suma as respostas dos subprocessos para obter a resposta final.

A abordagem dinâmica é chamada de Quadratura Adaptativa, porque a abordagem adapta por se mesma a forma da curva. Em particular, em lugares onde a curva é plana uma área ampla de trapézio pode se aproximar com a área da função. Em lugares onde a curva muda de forma, especificamente onde a tangente de f(x) é quase vertical, pequenos subproblemas serão gerados, conforme seja necessário.

(2)

3. Problema

Neste trabalho deve-se implementar algumas variantes do algoritmo da quadratura adaptativa [1], utilizando pthreads e OpenMP.

O programa deve utilizar X threads para computar a aproximação, pelo método de trapézios, da área abaixo da curva formada por uma função f.. O número de trapézios a ser calculado para realizar a aproximação depende de uma tolerância. A tolerância na diferença entre um trapézio e os dois subtrapézios “seguintes” deve ser fixada inicialmente em 10^-20, mas deve ser um valor facilmente alterado no programa.

1. Na primeira variante, cada thread calcula um subintervalo pelo qual será responsável e calcula o resultado para esse subintervalo inteiro. Quando todas as threads terminarem, a thread principal deve mostrar o resultado final.

2. Na segunda variante, a thread principal inicialmente cria uma lista de tarefas, contendo os extremos dos intervalos, com NUMINICIAL tarefas. Cada thread executa uma tarefa, e se ela gerar subtarefas, coloca uma delas na fila global e processa a outra, até que não encontre mais tarefas na fila (Escreva operaçoes InsereTarefa e RetiraTarefa para manipular essa fila.). A thread principal espera as demais terminarem e mostra o resultado final.

O programa deve aceitar facilmente a mudança para outras funções e intervalos, parametrizando o cálculo da integral pelos extremos e por um ponteiro de função.

Execute cada variante para diferentes números de threads (2, 4 e 8), com algumas medidas preliminares dos tempos obtidos para cada combinação. Deve-se experimentar com diferentes tolerâncias e com diferentes funções. (Nas próximas aulas iremos discutir questões relacionadas a medidas de desempenho.) Procure funções (pode inventar loops e muitas operações com doubles) que criem uma carga de processamento relevante. Elabore um pequeno relatório com seus resultados, explicitando também a arquitetura em que executou os testes. Discuta também o que considerou vantagens ou desvantagens de cada um dos ambientes.

Fazer o programa em C. Nas variantes em pthreads, não misture semáforos e monitores.

4. Solução sequencial da Quadratura Adaptativa

A seguir se apresenta a solução da quadratura adaptativa de forma sequencial. Esta solução presenta uma recursividade que ajuda a identificar as possíveis seções paralelizáveis, e outras que podem virar seções criticas, como por exemplo, a suma das áreas, no caso seja uma variável compartilhada.

1. void integral(double l, double r, double area) 2.{

3. double m = (r+l)/2.0; 4. double larea = trape_area(l,m); 5. double rarea = trape_area(m,r); 6. double sumOfAreas = larea + rarea; 7. double relError = fabs(area - sumOfAreas); 8. if(relError <= EPSILON)

9. { return sumTotal = sumTotal + sumOfAreas; } 10. else

11. { integral(l,m, larea) ; integral(m,r, rarea); } 12.}

13.

14.double trape_area(double a, double b) 15.{

16. double f_a = function(a); 17. double f_b = function(b); 18. double c = fabs(b-a); 19. return 0.5 * (f_a + f_b) * c; 20.}

(3)

No Algoritmo 1 os subproblemas gerados devem manter salvo o resultado de área do intervalo que processam e devolver a chamado ao subproblema principal (ver linha 9). A variável sumTotal mantém o resultado computado por cada subprocesso, no caso sequencial não terá concorrência de acessos, no entanto em um ambiente paralelo seria um problema que precisa-se tratar cuidadosamente.

A segunda variante do problema apresentado anteriormente, as seções paralelizáveis, podem ser facilmente detectadas na linha 11 do algoritmo. Uma delas pode ser resolvida por uma thread e outra agendada em uma fila global compartilhada pelas threads, assim alguma delas a resolverá em algum momentos. A primeira variante do problema depende muito da quantidade de tarefas definidas inicialmente, pois cada subintervalo gerado é atribuído a uma thread e resolvida usando uma recursividade como a definida no Algoritmo 1.

5. Solução da Quadratura Adaptativa usando pThread

5.1 Variante 1

No algoritmo 2 apresentamos a variante 1 resolvida com pthread para o problema definido na seção 3. Nas linhas 1-5 é definida uma estrutura de dados que representa um intervalo e na linha 7 um semáforo, ele ajuda a proteger a seção critica identificada na seção 4. Nas linhas 17-25 são criada uma quantidade de trabalhos segundo o número de thread definido previamente. Para cada thread criado é atribuída uma função Worker definida na linha 26, cada uma de estas funções resolve um intervalo atribuído na linha 19 do Algoritmo 2.

Cada Worker faz uma chamada à função integral da mesma que o algoritmo sequencial da seção 4, com a diferença que na linha 41-43 protegem a computação da área total.

1. typedef struct{ 2. double a; 3. double b; 4. double area; 5. }Interval; 6. ... 7. sem_t mutex; 8. ... 9. int main () 10. { 11. pthread_t work[NUMBER_THREAD]; 12. pthread_attr_t attr; 13. pthread_attr_init(&attr); 14. pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); 15. sem_init (&mutex, SHARED, 1);

16. . double width = fabs(b-a)/NUMBER_THREAD; 17. for(int i= 0; i < NUMBER_THREAD; i++) 18. {

19. Interval *inteval = create(a+ (width*i), a + (width*(i+1))); 20. pthread_create (&work[i], &attr, Worker, (void *) point); 21. }

22. for(int j= 0; j < NUMBER_THREAD; j++) 23. { pthread_join(work[j], NULL); } 24. printf("Total = %f\n", sumTotal); 25. }

26. void *Worker(void *arg) {

27. Interval *interval = (Interval*) arg;

28. integral(interval->a,interval->b, interval->area); 29. }

(4)

31. void integral(double r, double l, double area ) 32. { 33. double m,larea,rarea,sumOfAreas,relError; 34. m = (l+r)*0.5; 35. larea = trape_area(l,m); 36. rarea = trape_area(m,r); 37. sumOfAreas = larea + rarea; 38. relError = fabs(area - sumOfAreas); 39. if(relError <= EPSILON)

40. {

41. sem_wait(&mutex);

42. sumTotal = sumTotal + sumOfAreas; 43. sem_post(&mutex);

44. } 45. else

46. { integral(l,m,larea) ; integral(m,r,rarea); } 47.}

Algoritmo. 2 Quadratura Adaptativa Variante 1 com pThread 5.1 Variante 2

No algoritmo 3 apresentamos a variante 2 resolvida com pThread para o problema definido na seção 3. Para resolver esta variante é necessário uma estrutura de dados de tipo queue para armazenar as tarefas geradas pelas diferentes thread ( ver linhas 13-18 ). Nas linhas 1-7 se define uma estrutura de dados para representar as tarefas a processar (taskInterval). Três semáforos são necessários nesta solução, um para manter a integridade da suma das áreas (linha 22), outro para sincronizar o armazenamento de novas tarefas (linha 23) e mais um para sincronizar o final do processamento (linha 24).

Na linha 38 uma chamada a função createInitialTask ajuda a criar um número de tarefas iniciais. Nas linhas 96-103 é defina a função createInitialTask, ali novos sub-intervalos são gerados e empilhados na queue. Na linhas 39-41 são criados os diferentes thread que chamamos de consumer, que no final também fazem o role de producer, pois eles podem criar novas sub-tarefas. Para cada consumer[i] é atribuída a funão Consumer definida na linha 46, nesta função são aplicadas as diferentes regras de sincronização para resolver este problema, a seguir se definem estas regrras:

1) Manter a suma das tarefas, as linhas 84-86 ajudam a sincronizar a suma total da área gerada pelas diferentes sub-tarefas.

2) Armazenar novas tarefas, nas linhas 54-57 controlam o acesso coordenado da queue para extrair uma tarefa e processá-la, na linha 90-92 (dentro da chamada da função integral) é gerada uma nova subtarefa e empilhada na queue.

3) Sincronizar o final do processamento, as linhas 50-52 e 70-72 são necessárias para saber quantos thread estão processando alguma tarefa. As linhas 64-69 avaliam se o número total de thread é igual ao número total definido previamente, e também se na queue não tem nenhuma tarefa para processar. Depois disto último o processo acaba.

Na linha 60 a chamada para a função integral serve para processar as tarefas atribuídas em cada consumer[i]. Na linha 93 assegura que o mesmo cosumer[i] processe parte do intervalo inicial, e na linha 91 o cosumer[i] empilha uma sub-tarefa na queue global do processamento.

(5)

1. typedef struct taskInterval 2. {

3. double r; 4. double l; 5. double area;

6. struct taskInterval *next; 7. } taskInterval;

....

13. typedef struct queue 14. {

15. int contains; // no. of elements currently in the queue 16. struct taskInterval *front; // the front ptr

17. struct taskInterval *rear; // the rear ptr 18. } queue; 19. 20. queue *q; 21. 22. sem_t mutexsum; 23. sem_t mutexenqueue; 24. sem_t mutexidle; 25. 26. int main() 27. { 28. pthread_t consumer[NUMBER_THREAD]; 29. pthread_attr_t attr; 30. pthread_attr_init(&attr); 31. pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); 32. sem_init (&mutexsum, SHARED, 1);

34. sem_init (&mutexenqueue, SHARED, 1); 35. sem_init (&mutexidle, SHARED, 1); 36. queue_init ();

37. activeconsumer= 0; 38. createInitialTask(r, l);

39. for(int i= 0; i < NUMBER_THREAD; i++)

40. { pthread_create (&consumer[i], &attr, Consumer, (void *) &i); } 41.

42. for(int j= 0; j < NUMBER_THREAD; j++) 43. { pthread_join(consumer[j], NULL); } 44. printf("Total = %f\n", sumTotal); 45. }

46. void *Consumer(void *arg) 47. { 48. while(1) 49. { 50. sem_wait(&mutexidle); 51. idle++; 52. sem_post(&mutexidle);

53. struct taskInterval *task=NULL; 54. sem_wait(&mutexenqueue); 55. if(q->contains > 0) 56. { task = dequeue (); } 57. sem_post(&mutexenqueue); 58. if(task!=NULL) 59. {

60. integral(task->l, task->r, task->area); 61. free(task);

62. task =NULL; 63. }

64. sem_wait(&mutexidle);

65. if(idle == NUMBER_THREAD && q->contains==0) 66. { sem_post(&mutexidle); 67. break; 68. } 69. sem_post(&mutexidle); 70. sem_wait(&mutexidle); 71. idle--; 72. sem_post(&mutexidle);

(6)

73. } 74.}

75.void integral(double l, double r, double area) 76.{

77. double m,larea,rarea,sumOfAreas,relError; 78. m = (r+ l)*0.5;

79. larea = trape_area(l,m); 80. rarea = trape_area(m,r); 81. sumOfAreas = larea + rarea; 82. relError = fabs(area - sumOfAreas); 83. if(relError <= EPSILON)

83. {

84. sem_wait(&mutexsum);

85. sumTotal = sumTotal + sumOfAreas; 86. sem_post(&mutexsum); 87. } 88. else 89. { 90. sem_wait(&mutexenqueue); 91. enqueue(l,m,larea); 92. sem_post(&mutexenqueue); 93. integral(m,r,rarea); 94. } 95.}

96.void createInitialTask(double l, double r) 97.{

98. double width = fabs(r-l)/(double)NUMBER_TASK; 99. for(int i=0; i < NUMBER_TASK; i++)

100. {

101. enqueue (l+ (width*i), l + (width*(i+1)), -1.0); 102. }

103.}

Algoritmo. 3 Quadratura Adaptativa Variante 2 com pThread

6. Solução da Quadratura Adaptativa usando openMP

6.1 Variante 1

No Algoritmo 4 resolve a variante 1 usando openMP. Na linha 4 do Algoritmo 4 se define o número de threads usado no processamento, na linha 5 é computado o tamanho da altura dos trapézios, que no eixo x pode ser considerado como a largura de cada área atribuída para cada thread. Na linha 8 os intervalos são atribuídos para cada thread criado implicitamente por openMP.

Antes de seguir explicando é melhor definir cada #pragma usada por openMP para paralelizar o código nesta variante apresentada. Utilizando as diretivas #pragma disponíveis na linguagem, o programador explicita os locais do programa que serão paralelizados. A etapa de pré-compilação do programa converte as diretivas em chamadas para threads comuns, como pThreads por exemplo. O programador não precisa usar as APIs de threads diretamente, isto é feito pelo OpenMP. Por exemplo, o pragma #pragma omp parallel for usado no algoritmo 4 na linha 6 atribui cada intervalo a uma thread gerado implicitamente por openOMP. O pragma

#pragma omp critical usado na linha 33 serve para proteger a variável compartilhada entre eles e que mantém a suma total da área. Na linha 32 se apresenta a recursividade necessária para computar as possíveis subtarefas que cada thread gera.

(7)

1. int main () 2. {

3. omp_set_dynamic(0);

4. omp_set_num_threads(NUMBER_THREAD); 5. double width = fabs(r-l)/NUMBER_THREAD; 6. #pragma omp parallel for

7. for (int i=0; i < NUMBER_THREAD; i++)

8. { paralelIntegral(l+ (width*i), l + (width*(i+1))); } 9. printf("Total = %f\n", sumTotal);

10. return 0; 11. }

12. void paralelIntegral(double l , double r) 13. {

14. double area = trape_area(l,r); 15. integral(l,r,area);

16. }

17. void integral(double l, double r, double area ) 18. {

19. double m,larea,rarea,sumOfAreas,relError; 20. m = (l+r)*0.5;

21. larea = trape_area(l,m); 22. rarea = trape_area(m,r); 23. sumOfAreas = larea + rarea; 24. relError = fabs(area - sumOfAreas); 25. if(relError <= EPSILON)

26. {

27. #pragma omp critical

28. sumTotal = sumTotal+ sumOfAreas; 29. } 30. else 31. { 32. integral(a,m,larea) ; integral(m,b,rarea); 35. } 36. }

Algoritmo. 4 Quadratura Adaptativa Variante 1 com openOMP 6.1 Variante 2

A variante 2 resolvida com openMP é apresentada no algoritmo 5. Neste algoritmo o openOMP faz a gestão de produtor consumidor implicitamente. Nas linhas 6-10 o pragma #pragma omp parallel for ajuda a criar um número de threads definido, um intervalo é atribuído a cada thread para ela resolver.

Na linha 33 o pragma #pragma omp task untied é usado para criar novas tarefas para alguma outra thread resolver. Neste pragma é usado a tag untied, que significa qualquer outra thread pode executar o porção de código ligada a ela. Por tanto, nas linhas 33-34 uma nova tarefa é criada e armazenada em um pool de tarefas. Assim, se alguma thread está disponível atendera está nova tarefa. Por último, a linha 28 protege a variável que mantém consistente a suma das áreas.

1. int main() 2. { 3.

4. omp_set_num_threads(NUMBER_THREAD); 5. double width = fabs(r-l)/NUMBER_THREAD; 6. #pragma omp parallel for

7. for (int i=0; i < NUMBER_THREAD; i++) 8. { parallelIntegral(l+ (width*i), l + (width*(i+1))); } 9. printf("Total = %f\n", sumTotal); return 0; 10. }

11. void parallelIntegral(double l , double r) 12. {

(8)

13. double area = trape_area(l,r); 14. integral(l,r,area);

15. }

17. void integral(double l, double r, double area ) 18. {

19. double m,larea,rarea,sumOfAreas,relError; 20. m = (l+r)*0.5;

21. larea = trape_area(l,m); 22. rarea = trape_area(m,r); 23. sumOfAreas = larea + rarea; 24. relError = fabs(area - sumOfAreas); 26. if(relError <= EPSILON)

27. {

28. #pragma omp critical 29. sum = sum + sumOfAreas; 30. }

31. else 32. {

33. #pragma omp task untied 34. integral(l,m,larea) ; 35. integral(m,r,rarea); 36. }

37. }

Algoritmo. 5 Quadratura Adaptativa Variante 1 com openOMP

7. Comparação das variantes em openMP e pThread.

Nesta seção apresenta os resultados dos diferentes testes usando OpenMP e pThread, diferentes opções são usadas para rodar as variantes da quadratura adaptativa. A figura 2 representa os diferentes testes rodados para encontrar a área da função e^(x), no intervalo [0,15], a seguir a se define a integral desta função:

Figura 1 Integral da função e^x

Na figura 2 são apresentados os comportamentos de cada teste. Nestes gráficos cada linha representa uma diferente configuração, por exemplo, “C2-T2” significa que foi rodado dois thread em dois cores. Assim, em total foi rodado para cada variante 9 diferentes testes. O eixo “X” da imagem é define o EPSILON permitido na computação das diferentes áreas . O eixo “Y” representa o tempo em segundos que gasta cada teste.

(9)

(a) Variante 1

(b) Variante 2

Figura 2. Apresenta uma comparativa entre número de cores (Cx) e o número de threads por core (Tx) usadas em pThread, isto para a variante 1 (a) e para variante 2 (b).

Pode se observar que as linhas nas Figura 2 cresce seguindo a forma da função original, a forma da função e^x faz que o processamento se incremente rapidamente. Quanto a função cresça novas subtarefas são geradas e o tempo de processamento aumenta.

Da Figura 2, pode-se ressaltar que o teste C8-T2, para ambas variantes, se mantem estável durante o processamento. Um mesmo comportamento segue o teste C8-T4 que parece ter uma relação entre a quantidade usada para executar alguma tarefa e os possíveis recursos necessários para mente a execução ativa.

Os testes para openMP seguem o mesmo padrão definido anteriormente. Da mesma forma, as linhas da figura 3 também seguem o comportamento da função e^x. A principal observação, comparando as duas variantes 1 (ver figura 2 e 3), em OpenMP a solução para a primeira variante gasta mais tempo que a variante 2. Na variante 2 openMP faz a gestão implícita do produtor/consumidor e em pThread um abstração é definida para compartilhar a bolsa de tarefas.

(b) Variante 1 (a) Variante 2

Figura 3. Apresenta uma comparativa entre número de cores (Cx) e o número de threads por core (Tx) usadas em openOMP, isto para a variante 1 (a) e para variante 2 (b).

As figuras4,5 apresentam uma comparativa entre as duas bibliotecas na computação da integral da função e^x no intervalo [0,15]. Para o desenho de esta figura foram considerados os testes que gastaram maior tempo de processamento.

(10)

Figura 4 Comparativa entre PThread e openOMP para a Variante 1.

Figura 5 Comparativa entre PThread e openOMP para a Variante 2.

Estas imagens reflexam que OpemMP uma vantagen sobre pThread nos diferentes testes desenvolvidos neste trabalho.

8. Vantagens ou Desvantagens

Sobre OpenMP, ela resulta fácil de usar e não precisa de conceitos avançados para definir um código paralelizável. Por outro lado, em pThread é necessário definir com atenção os componentes que serão coordenados no processamento, um ponto positivo de pThread é que o programados tem conhecimento de como o processamento está sendo feito.

9. Referências

[1]. G. Andrews. Paradigms for process interaction in distributed programs. ACM Computing Surveys, 23(1), Mar. 1991, pp. 49–90.

Referências

Documentos relacionados

No contexto em que a Arte é trabalhada como recurso didático-pedagógico na Educação Matemática (ZALESKI FILHO, 2013), pode-se conceber Performance matemática (PM) como

O Convênio, que certamente trará benefícios mútuos na condução das atividades de supervisão em ambos os países, propiciará, sem dúvida, um incremento na troca

libras ou pedagogia com especialização e proficiência em libras 40h 3 Imediato 0821FLET03 FLET Curso de Letras - Língua e Literatura Portuguesa. Estudos literários

Ninguém quer essa vida assim não Zambi.. Eu não quero as crianças

A elaboração, correção e aplicação de provas de segunda chamada, quando cobradas pela escola, a título de taxa extraordinária, serão pagas ao professor na proporção de

Baseando-se em características morfológicas, Oldeman (1983) sugeriu um método relativamente simples para categorização das árvores: árvores do presente, do passado e do

Dentre todas as atividades a que merece destaque, foi atividade com música, pois, os alunos se entusiasmaram e me surpreenderam cantando músicas de diversos

Sair à noite é divertido mas os locais para onde vais devem ter a preocupação de cumprir os requisitos de segurança contemplados nas Normas, para que a diversão não