Mochila Binária com OpenMP

Tamanho: px
Começar a partir da página:

Download "Mochila Binária com OpenMP"

Transcrição

1 UNIVERSIDADE FEDERAL DE SANTA MARIA CENTRO DE TÉCNOLOGIA CURSO DE CIÊNCIA DA COMPUTAÇÃO Mochila Binária com OpenMP Cícero Augusto de Lara Pahins, Cristiano Reis dos Santos. Professora: Profª Andrea Schwertner Charão. Disciplina: Programação Paralela.

2 1. Mochila Binária O problema da mochila (Knapsack problem) é um problema de optimização combinatória. O nome dá-se devido ao modelo de uma situação em que é necessário preencher uma mochila com objetos de diferentes pesos e valores. O objetivo é que se preencha a mochila com o maior valor possível, não ultrapassando o peso máximo. O problema da mochila é um dos 21 problemas NP-completos de Richard Karp. A formulação do problema é extremamente simples, porém sua solução é mais complexa. Este problema é a base do primeiro algoritmo de chave pública (chaves assimétricas). O Problema da Mochila está relacionado com um grande número de outros modelos de programação. Podemos entendê-lo de uma forma muito simples da seguinte forma: Um viajante dispõe de apenas uma mochila para sua viajem. Essa mochila possui uma dada capacidade e deve ser preenchida com alguns objetos que lê serão uteis na viajem. Cada objeto possui um peso e um dado valor. O objetivo e escolher quais objetos devem ser levados não ultrapassando a capacidade da mochila de forma a maximizar o valor contido na mochila. O problema da mochila possui alguns variantes, tais como: O problema simples: Em que o ladrão possui uma mochila e um objeto de cada tipo com o seu valor. O problema múltiplo: Em que o ladrão possui mais de uma mochila ou uma mochila com vários bolsos e vários objetos de cada tipo com o seu valor. 2. Objetivo do Trabalho O objetivo geral deste trabalho é usar Openmp para paralelizar o programa sequencial fornecido, buscando obter ganho de desempenho. 3. Análise do Código A aplicação proposta soluciona uma versão do problema da mochila conhecido como mochila binaria. Seja o peso do - ésimo objeto e seu valor. Se o objeto aparece na mochila, então = 1 caso contrario = 0. Se denotarmos por a capacidade da mochila, e por a quantidade de objetos disponíveis para escolha e como o maior valor obtido para a mochila de capacidade usando os objetos, então o problema pode ser formulado algebricamente como a seguir: = >0 ; >0 Onde: = 0 ou = 1.

3 O programa sequencial disponibilizado está implementado na linguagem C. Ele é composto por quatro arquivos. Sendo um representando a função principal, dois implementam uma solução para obtenção do tempo de execução e o ultimo contem alguns DEFINES utilizados pela aplicação. A solução proposta pela aplicação é construir uma tabela de dimensão, indexada pela capacidade e numero de livros. Onde, é o máximo valor que pode se obter com a capacidade usando de 1 até livros. Supondo que temos uma mochila com capacidade, e queremos saber qual o máximo valor usando livros de 1 até. Nós podemos usar o livro ou não, se decidirmos usarmos então o máximo valor é mais 1 que pode ser obtido com o restante da capacidade e dos livros. Se decidirmos não usar o livro, então a o máximo valor é, 1.Isso pode ser obtido recursivamente: +, 1, =, 1 Apesar de a solução proposta ser recursiva sua implementação foi feita de forma interativa. 4. Abordagens Utilizadas Para os Testes de Desempenho Para analisar o desempenho dos programas desenvolvidos foram utilizadas duas abordagens. A primeira foi o fator de aceleração conhecido como speedup. E a segunda abordagem foi cálculo da eficiência. SPEEDUP O speedup refere-se ao quanto mais rápido é a aplicação paralelizada em relação ao algoritmo sequencial. Onde: = é o número de processadores. é o tempo de execução com o algoritmo sequencial. é o tempo de execução com o algoritmo paralelo com p processadores.

4 EFICIÊNCIA A eficiência estima o quão bem estão sendo utilizados os processadores para resolver o problema. = 5. Teste de Desempenho Foram definidos 5 casos de teste, ú, h, ocorrendo variação no número de livros e na capacidade da mochila. Os casos de testes foram: 1 5,2 ; 2 5, 1 ; 3 10,5.000; 4 5,5.000 ; 5 1, , speedup

5 1.200 Eficiência , speedup

6 1.200 Eficiência , speedup

7 1.200 Eficiência ,5.000 speedup

8 1.200 Eficiência ,5.000 speedup

9 1.200 Eficiência Análise dos resultados A partir de um script, foram realizadas sucessivas rodadas dos programas para calcular o desempenho. Foi utilizada a função de tempo do OpenMP _ _ para medir o tempo de execução. Os tempos de execução para cada rodada do programa foram salvos em arquivos. Esses dados foram posteriormente lidos por outro programa em C++ que calculou o speedup e a eficiência para os diferentes casos de teste utilizados. Esses resultados foram apresentados anteriormente em forma de gráficos. Todos os programas utilizados estão disponíveis na página do trabalho. 6.1 Análise versão 1 Os speedups para os 5 casos de teste utilizados, apresentaram uma melhora de performance em relação a execução sequencial a partir de duas threads. Para a maioria dos casos de teste o melhor desempenho concentrou-se entre 3 e 6 threads. Com 8 threads o desempenho para a maioria teve uma queda de performance, ficando mais lento que a execução sequencial. Acredita-se que a razão para isso é o grande volume de threads geradas no comando paralisado na solução. O número de divisões de trabalho entre as threads neste laço gera um tempo maior devido ao gerenciamento realizado. O cálculo da eficiência demonstrou que com o aumento das threads diminuiu bastante o processamento utilizado dos processadores. Apesar da diminuição do tempo de execução os processadores não estão sendo utilizados eficientemente.

10 6.2 Análise versão 2 A segunda versão apresentou um speedup praticamente sequencial, não apresentando ganho de desempenho. Isso possivelmente ocorreu pois é utilizada a diretiva a qual define uma região que deve ser executada sequencialmente em um programa paralelo. Assim como a região onde foi utilizada apesar de ser paralelizada executou em paralelo, por possuir dependências de valores. A utilização dos processadores com o aumento das threads foi tornando-se cada vez mais ineficiente. 7. Códigos #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <omp.h> double omp_get_wtime(void); int max_random_weight = 2000; int max_random_profit = 50; int *weight, *profit, *total, *use_book; double _start1 = 0.0f, _end1 = 0.0f; void init_books (int n_book, int n_bag); int solve (const int n_books, const int bag_cap); void backtrack (const int n_books, const int bag_cap); int main(int argc, char** argv){ int total_profit = 0, total_weight = 0, book_i = 0, n_sold = 0, n_books = 0, bag_cap = 0, NUMTHREADS = 0; NUMTHREADS = omp_get_max_threads(); _start1 = omp_get_wtime(); n_books = atoi(argv[1]); bag_cap = atoi(argv[2]); weight = malloc(n_books * sizeof(int)); profit = malloc(n_books * sizeof(int)); use_book = malloc(n_books * sizeof(int)); total = malloc((bag_cap + 1) * n_books * sizeof(int)); init_books(n_books, bag_cap); total_profit = solve ( n_books, bag_cap); backtrack (n_books, bag_cap); total_weight = n_sold = 0;

11 for (book_i = 0; book_i < n_books; ++book_i){ if (use_book[book_i]) { total_weight += weight[book_i]; total_profit -= profit[book_i]; ++n_sold; _end1 = omp_get_wtime(); if (total_profit!= 0){ printf("%d\n",numthreads); printf("%d\n", n_books); printf("%d\n", bag_cap); printf ("%f\n",(_end1 - _start1)); return 0; void init_books (int n_book, int n_bag){ srand48 ( L); int i = 0; for (i = 0 ; i < n_book ; ++i) { weight[i] = 1 + (int)(max_random_weight * drand48()); profit[i] = 1 + (int)(max_random_profit * drand48()); int solve (const int n_books, const int bag_cap){ int book_i, cap_j; int* prev_book_total, *book_total; #pragma omp parallel private(cap_j) shared(weight, total, profit) { #pragma omp for schedule (static) for (cap_j = 0 ; cap_j <= bag_cap; ++cap_j) { if (weight[0] > cap_j) total[cap_j] = 0; else total[cap_j] = profit[0]; prev_book_total = &total[0]; book_total = &total[bag_cap+1]; for (book_i = 1; book_i < n_books; ++book_i) { const int wi = weight[book_i]; #pragma omp parallel private(cap_j) shared(weight, book_total, profit, prev_book_total) { #pragma omp for schedule (static) for (cap_j = 0; cap_j <= bag_cap; ++cap_j) { int new_profit; if (cap_j < weight[book_i]) { book_total[cap_j] = prev_book_total[cap_j]; continue; new_profit = profit[book_i] + prev_book_total[cap_j - wi]; book_total[cap_j] = (prev_book_total[cap_j] >= new_profit? prev_book_total[cap_j] : new_profit);

12 prev_book_total = book_total; book_total += (bag_cap+1); return prev_book_total[bag_cap]; void backtrack (const int n_books, const int bag_cap) { int book_i, off; const int* cur_total; memset (use_book, 0, n_books * sizeof(int)); cur_total = &total[n_books * (bag_cap+1) - 1]; { for (book_i = n_books-1; book_i > 0; --book_i, cur_total -= (bag_cap+1)) if (*cur_total!= *(cur_total - (bag_cap+1))) { use_book[book_i] = 1; cur_total -= weight[book_i]; use_book[0] = (*cur_total > 0); //////////////////////////////////// #include <iostream> #include <cstdlib> #include <climits> #include <cstring> #include <cstdio> #include <ctime> //////////////////////////////////// #include <omp.h> //////////////////////////////////// int solve(const int numbooks, const int bag_cap, const int *weight, const int *profit, int *total); void backtrack(const int n_books, const int bag_cap, const int *weight, const int *total, int *use_book); void init_books(const int n, const int max_random_weight, const int max_random_profit, int *weight, int *profit); //////////////////////////////////// int main(int argc, char** argv) { int total_profit = 0, total_weight = 0,NUMTHREADS = 0; int max_random_weight = 2000, max_random_profit = 50; int n_books = 10000, bag_cap = 5000; double wtime_start = 0.0, wtime_end = 0.0; NUMTHREADS = omp_get_max_threads();

13 wtime_start = omp_get_wtime(); n_books = atoi(argv[1]); bag_cap = atoi(argv[2]); int* weight = new int[n_books]; int* profit = new int[n_books]; int* use_book = new int[n_books]; int* total = new int[(bag_cap + 1) * n_books]; init_books(n_books, max_random_weight, max_random_profit, weight, profit); total_profit = solve(n_books, bag_cap, weight, profit, total); backtrack(n_books, bag_cap, weight, total, use_book); //printf("%lf\n", wtime_end - wtime_start); //printf("total_profit: %d\n", total_profit); for (int book_i = 0; book_i < n_books; book_i++) { if (use_book[book_i]) { total_weight += weight[book_i]; total_profit -= profit[book_i]; if (total_profit!= 0) { //printf("error"); else { //printf("ok"); wtime_end = omp_get_wtime(); printf("%d\n",numthreads); printf("%d\n", n_books); printf("%d\n", bag_cap); printf ("%f\n",(wtime_end - wtime_start)); //////////////////////////////////// void init_books(const int n, const int max_random_weight, const int max_random_profit, int *weight, int *profit) { srand(0); for (int i = 0; i < n; i++) { weight[i] = 1 + static_cast<int>(rand()) % max_random_weight; profit[i] = 1 + static_cast<int>(rand()) % max_random_profit; //////////////////////////////////// int solve(const int numbooks, const int bag_cap, const int *weight, const int *profit, int *total) { int cap_j; int *prev_book_total, *book_total; // case for using only book 1 #pragma omp parallel shared(total, profit, weight) {

14 #pragma omp for private(cap_j) schedule(static) for (cap_j = 0; cap_j <= bag_cap; cap_j++) { if (weight[0] > cap_j) { total[cap_j] = 0; else { total[cap_j] = profit[0]; prev_book_total = &total[0]; book_total = &total[bag_cap+1]; int book_i = 1; #pragma omp parallel shared(total, profit, weight) private(prev_book_total, book_total) { #pragma omp for ordered private(book_i) for (book_i = 1; book_i < numbooks; book_i++) { const int wi = weight[book_i]; prev_book_total = &total[(book_i-1)*(bag_cap+1)]; book_total = &total[book_i*(bag_cap+1)]; /* book_total[j] is total[book_i, j], prev_book_total[j] is total[book_i-1, j]*/ int new_profit; #pragma omp ordered { for (cap_j = 0; cap_j <= bag_cap; cap_j++) { if (cap_j < weight[book_i]) { /* Doesn't fit. */ book_total[cap_j] = prev_book_total[cap_j]; else { new_profit = profit[book_i] + prev_book_total[cap_j - wi]; book_total[cap_j] = (prev_book_total[cap_j] >= new_profit? prev_book_total[cap_j] : new_profit); return total[(numbooks - 1 )*(bag_cap+1) + bag_cap]; //////////////////////////////////// void backtrack(const int n_books, const int bag_cap, const int *weight, const int *total, int *use_book) { const int* cur_total; memset (use_book, 0, n_books * sizeof(int)); cur_total = &total[n_books * (bag_cap + 1) - 1]; int book_i = n_books-1; { for (book_i = n_books-1; book_i > 0; --book_i, cur_total -= (bag_cap+1)) if (*cur_total!= *(cur_total - (bag_cap+1))) { use_book[book_i] = 1; cur_total -= weight[book_i]; use_book[0] = (*cur_total > 0);

15 ////////////////////////////////

16 8. Referências