• Nenhum resultado encontrado

Implementações Paralelas para os Problemas do Fecho Transitivo e Caminho Mínimo APSP na GPU

N/A
N/A
Protected

Academic year: 2021

Share "Implementações Paralelas para os Problemas do Fecho Transitivo e Caminho Mínimo APSP na GPU"

Copied!
118
0
0

Texto

(1)

U

NIVERSIDADE

F

EDERAL DE

G

OIÁS

I

NSTITUTO DE

I

NFORMÁTICA

R

OUSSIAN

D

I

R

AMOS

A

LVES

G

AIOSO

Implementações Paralelas para os

Problemas do Fecho Transitivo e

Caminho Mínimo APSP na GPU

Goiânia 2014

(2)

U

NIVERSIDADE

F

EDERAL DE

G

OIÁS

I

NSTITUTO DE

I

NFORMÁTICA

A

UTORIZAÇÃO PARA

P

UBLICAÇÃO DE

D

ISSERTAÇÃO

EM

F

ORMATO

E

LETRÔNICO

Na qualidade de titular dos direitos de autor, AUTORIZO o Instituto de Infor-mática da Universidade Federal de Goiás – UFG a reproduzir, inclusive em outro formato ou mídia e através de armazenamento permanente ou temporário, bem como a publicar na rede mundial de computadores (Internet) e na biblioteca virtual da UFG, entendendo-se os termos “reproduzir” e “publicar” conforme definições dos incisos VI e I, respectiva-mente, do artigo 5oda Lei no9610/98 de 10/02/1998, a obra abaixo especificada, sem que me seja devido pagamento a título de direitos autorais, desde que a reprodução e/ou publi-cação tenham a finalidade exclusiva de uso por quem a consulta, e a título de divulgação da produção acadêmica gerada pela Universidade, a partir desta data.

Título: Implementações Paralelas para os Problemas do Fecho Transitivo e Caminho Mínimo APSP na GPU

Autor(a): Roussian Di Ramos Alves Gaioso

Goiânia, 13 de Fevereiro de 2014.

Roussian Di Ramos Alves Gaioso – Autor

(3)

R

OUSSIAN

D

I

R

AMOS

A

LVES

G

AIOSO

Implementações Paralelas para os

Problemas do Fecho Transitivo e

Caminho Mínimo APSP na GPU

Dissertação apresentada ao Programa de Pós–Graduação do Instituto de Informática da Universidade Federal de Goiás, como requisito parcial para obtenção do título de Mestre em Ciência da Computação.

Área de concentração: Ciência da Computação. Orientador: Prof. Wellington Santos Martins

Goiânia 2014

(4)

R

OUSSIAN

D

I

R

AMOS

A

LVES

G

AIOSO

Implementações Paralelas para os

Problemas do Fecho Transitivo e

Caminho Mínimo APSP na GPU

Dissertação defendida no Programa de Pós–Graduação do Instituto de Informática da Universidade Federal de Goiás como requisito parcial para obtenção do título de Mestre em Ciência da Computação, aprovada em 13 de Fevereiro de 2014, pela Banca Examinadora constituída pelos professores:

Prof. Wellington Santos Martins Instituto de Informática – UFG

Presidente da Banca

Prof. Hugo Alexandre Dantas Nascimento Instituto de Informática – UFG

Prof. Edson Norberto Cárceres Faculdade de Computação –UFMS

Prof. Thierson Couto Rosa Instituto de Informática – UFG

(5)

Todos os direitos reservados. É proibida a reprodução total ou parcial do trabalho sem autorização da universidade, do autor e do orientador(a).

Roussian Di Ramos Alves Gaioso

Graduou-se em Sistema de Informação na UEG - Universidade Estadual de Goiás. Ao longo da carreira, foi Desenvolvedor e Analista de Sistema.

(6)

Aos meus pais, irmãos e esposa pelo apoio e alegria em todos os momentos. Ao orientador pela paciência no decorrer deste trabalho.

(7)

Agradecimentos

Aos meus pais e irmãos, que merecem os créditos pelo apoio que sempre me incentivaram a atingir meus objetivos.

A minha esposa que me apoiou e motivou na concretização do presente trabalho. Ao professor Wellington, que dedicou de seu tempo e compartilhou seu conhe-cimento, cujas orientações permitiram que este trabalho se concretizasse.

(8)

O primeiro e indispensável passo para obter as coisas que você deseja da vida é decidir o que você quer.

Ben Stein, Utilizando UML e Padrões.

(9)

Resumo

Gaioso, Roussian Di Ramos Alves. Implementações Paralelas para os Proble-mas do Fecho Transitivo e Caminho Mínimo APSP na GPU. Goiânia, 2014. 116p. Dissertação de Mestrado. Instituto de Informática, Universidade Federal de Goiás.

Este trabalho apresenta implementações paralelas baseadas em Graphics Processing Unit (GPU) para os problemas da identificação dos caminhos mínimos entre todos os pares de vértices e do fecho transitivo em um grafo. As implementações são baseadas nos principais algoritmos sequenciais e tiram o máximo proveito da arquitetura multithreaded das GPUs atuais. Nossa solução reduz a comunicação entre a Central Processing Unit (CPU) e a GPU, melhora a utilização dos Streaming Multiprocessors (SMs) e faz um uso intensivo de acesso aglutinado em memória para otimizar o acesso de dados do grafo. As vantagens dessas implementações propostas são demonstradas por vários grafos gerados aleatoriamente utilizando a ferramenta GTgraph. Grafos contendo milhares de vértices foram gerados e utilizados nos experimentos. Nossos resultados confirmam que implementações baseadas em GPU podem ser viáveis mesmo para algoritmos de grafos cujo acessos à memória e distribuição de trabalho são irregulares e causam dependência de dados.

Palavras–chave

GPU, GPGPU, CUDA, Teoria dos Grafos, Fecho Transitivo, APSP, Caminho Mínimo, BFS, Warshall, Dijkstra, FloydWarshall, Paralelismo

(10)

Abstract

Gaioso, Roussian Di Ramos Alves. Parallel Implementations for Transitive Closure and Minimum Path APSP Problems in GPU. Goiânia, 2014. 116p. MSc. Dissertation. Instituto de Informática, Universidade Federal de Goiás.

This paper presents a Graphics Processing Unit (GPU) based parallels implementations for the All Pairs Shortest Paths and Transitive Closure problems in graph. The imple-mentations are based on the main sequential algorithms and takes full advantage of the highly multithreaded architecture of current manycore GPUs. Our solutions reduces the communication between CPU and GPU, improves the Streaming Multiprocessors (SMs) utilization, and makes intensive use of coalesced memory access to optimize graph data access. The advantages of the proposed implementations are demonstrated for several graphs randomly generated using the widely known graph library GTgraph. Graphs con-taining thousands of vertices and different edges densities, varying from sparse to com-plete graphs, were generated and used in the experiments. Our results confirm that GPU implementations can be competitive even for graph algorithms whose memory accesses and work distribution are both irregular and data-dependent.

Keywords

GPU, GPGPU, CUDA, Graph Theory, Transitive Closure, APSP, Warshall, BFS, FloydWarshall, Dijkstra, Parallel, Minimum Path, Parallelism.

(11)

Sumário

Lista de Figuras 11

Lista de Tabelas 14

Lista de Algoritmos 16

Lista de Abreviaturas e Siglas 17

1 Introdução 19

2 Computação Paralela 22

2.1 Conceitos Preliminares 22

2.2 Taxonomia de Computadores 25

2.3 Modelos Computacionais Paralelos 30

2.3.1 Parallel Random Acess Machine (PRAM) 32

2.3.2 Modelos de Rede 34

2.3.3 Bulk Synchronous Parallel (BSP) 34

2.4 Medidas de Desempenho 36

3 Unidade de Processamento Gráfico para Propósito Geral 39

3.1 Conceitos Preliminares 39

3.2 Hardware CUDA 40

3.3 Modelo de Programação CUDA 44

3.4 Otimizações em CUDA 48

4 Algoritmos em Grafos 51

4.1 Conceitos Preliminares 51

4.2 Fecho Transitivo 53

4.2.1 Algoritmo Sequencial de Warshall 56

4.2.2 Algoritmo Sequencial de Busca em Largura 57

4.2.3 Outras Abordagens 59

4.3 Caminho Mínimo entre Todos os Pares de Vértices 61

4.3.1 Algoritmo Sequencial de Floyd-Warshall 62

4.3.2 Algoritmo Sequencial de Dijkstra 63

(12)

5 Implementações Propostas 69

5.1 Soluções Paralelas baseadas nos Algoritmos de Warshall e Floyd-Warshall 69

5.2 Solução Paralela baseada no Algoritmo BFS para o Problema do Fecho Transitivo 78

5.3 Solução Paralela baseada no Algoritmo Dijkstra para o Problema APSP 85

6 Resultados Experimentais 92

6.1 Materiais e Métodos 92

6.2 Resultados dos Algoritmos de Fecho Transitivo 93 6.2.1 Resultados das Implementações Paralelas do Warshall 93

6.2.2 Resultados da Implementação Paralela do BFS 94

6.2.3 Análise Comparativa dos Resultados das Implementações Paralelas Propostas 98

6.3 Resultados dos Algoritmos do Caminho Mínimo APSP 100 6.3.1 Resultados das Implementações Paralelas do Floyd-Warshall 100 6.3.2 Resultados das Implementações Paralelas de Dijkstra 101 6.3.3 Análise Comparativa dos Resultados das Implementações Paralelas Propostas 104

7 Conclusão 111

(13)

Lista de Figuras

2.1 Execução de um algoritmo sequencial em uma Unidade de Processamento. 23

2.2 Execução de um algoritmo paralelo em várias Unidades de Processamento. 23

2.3 Modelo Computacional de von Neumann 24

2.4 Taxonomia de Flynn 25

2.5 Categoria SISD 26

2.6 Categoria SIMD 26

2.7 Categoria MIMD 27

2.8 Taxonomia de Flynn-Johnson 28

2.9 Arquitetura Acesso Uniforme à Memória (UMA) 29

2.10 Arquitetura Acesso Não-Uniforme à Memória (NUMA) 30

2.11 Arquitetura de Acesso Não-Uniforme à Memória (NUMA) 30

2.12 Máquina de Acesso Aleatório (RAM) 31

2.13 Máquina Paralela de Acesso Aleatório (RAM) 32

2.14 Modelo Bulk Synchronous Parallel (BSP) 35

2.15 Super-etapa do BSP: computação, comunicação e sincronização. 36

2.16 Lei de Amdahl [15] 37

2.17 Lei de Gustafson [15] 38

3.1 Arquitetura CPU/GPU simplificada [40]. 40

3.2 Estrutura do Processador de Fluxo (SP). 41

3.3 Estruturas dos SMs de CUDA, sendo que (a) é a geração Ker pler com

192 SPs e (b) é a geraçãoFermicom 32 SPs 42

3.4 Arquitetura Geral dos SMs [8]. 44

3.5 Execução de um programa CUDA [21]. 45

3.6 Hierarquia dethreadsno modelo de programação CUDA. 46

3.7 Blocos dethreadsda grade executando o mesmo código, mas com fluxos

diferentes. 47

3.8 Hierarquia de Memória do Modelo de Programação CUDA [9]. 48

3.9 Algoritmo de soma de vetores em CUDA. 49

4.1 Exemplos de dois grafos, sendo o grafo (a) não-direcionado e o grafo (b)

direcionado 52

4.2 Representações de Grafos: (a) Matriz de Adjacência e (b) Lista de

Adja-cência 53

4.3 Representação de grafo usando a lista compactada. 54

4.4 Grafo original em (a) e seu respectivo fecho transitivo em (b). 54

4.5 Sequência de etapas para resolver o problema do Fecho Transitivo 55

4.6 Exemplo de execução do BFS em um grafo 59

(14)

4.8 Solução sequencial proposta por Venkataraman et al. [39]. 66

4.9 Solução paralela do algoritmo Floyd-Warshall proposta por Harish e

Na-rayanan [16]. 66

4.10 Visão geral do algoritmo proposto por Katz e Kider [20]. 68

5.1 Exemplo de ocupação intensiva dos SM’s: (a) Número de blocos de

thread; (b) Divisão das partes da matriz por bloco dethreads. 71

5.2 Iterações da implementação paralela descrita no Algoritmo 5.2 75

5.3 Primeira etapa da Fase 1 da implementação do Floyd-Warshall. 76

5.4 Segunda etapa da Fase 1 da implementação do Floyd-Warshall. 77

5.5 Paralelização de vários algoritmos BFS na GPU 79

5.6 Listas das Fronteiras do algoritmo paralelo proposto 80

5.7 Divisão de trabalhos entre aswar ps 81

5.8 Paralelização de visitas a vértices contidos nas fronteiras 82

5.9 Comunicação entre asthreadsativas de uma war psobre uma parte da

lista da fronteira atual 84

5.10 Execução simultânea de vários algoritmos de Dijkstra. 85

5.11 Atualização do menor caminho entre fronteiras. 86

6.1 Ganho computacional da implementação proposta com ocupação inten-siva em relação às implementações de Harish e Narayanan [16]; e Katz e

Kider [20]. 94

6.2 Ganhos computacionais das implementações paralelas do Warshall em

relação ao sequencial. 95

6.3 Ganho computacional da implementação paralela do BFS em relação ao algoritmo sequencial com grafos randômicos de600|V |e6000|V |arestas. 96

6.4 Tempo computacional (s) da implementação paralela do BFS com grafos

randômicos de6|V |,600|V |e6000|V |arestas. 98

6.5 Tempo computacional (s) do algoritmo sequencial do BFS com grafos

randômicos de 6000|V |arestas. 99

6.6 Comparação de tempo entre os algoritmos paralelos propostos do Warshall em Blocos e do BFS com grafos randômicos (gerador Random). 100

6.7 Comparação de tempo entre os algoritmos paralelos propostos do Warshall com Ocupação Intensiva e do BFS com grafos randômicos

(ge-rador Random). 101

6.8 Comparação de tempo entre os algoritmos paralelos propostos do Warshall em Blocos e do BFS com grafos de diâmetro≤ 10(R-MAT). 102

6.9 Comparação de tempo entre os algoritmos paralelos propostos do Warshall com Ocupação Intensiva e do BFS com grafos de diâmetro≤ 10

(R-MAT). 104

6.10 Tempo de execução dos algoritmos para grafos com 1024, 2048, 3072,

4096 e 5120 vértices. 106

6.11 Tempo de execução dos algoritmos para grafos com 6144, 7168, 8192 e

16384 vértices. 106

6.12 Ganhos despeedupem relação às implementações de Harish et al. [16]

e Katz e Kider [20]. 107

6.13 Ganhos despeedupem relação à implementação sequencial do algoritmo

(15)

6.14 Tempo computacional (s) do algoritmo sequencial do Dijkstra com grafos

de6000|V |arestas. 108

6.15 Ganho computacional da implementação paralela do Dijkstra com grafos

randômicos de6|V |,600|V |e6000|V |arestas. 109

6.16 Tempo computacional (s) da implementação paralela do Dijkstra com grafos randômicos de6|V |,600|V |e6000|V |arestas. 109

6.17 Comparação de tempo entre os algoritmos paralelos propostos do Floyd-Warshall com Dijkstra paralelo com grafos randômicos (Random). 110

6.18 Comparação de tempo entre os algoritmos paralelos propostos do Floyd-Warshall com Dijkstra paralelo com grafos de diâmetro≤ 10(R-MAT). 110

(16)

Lista de Tabelas

6.1 Tempo computacional (s) das implementações paralelas do algoritmo do

Warshall 94

6.2 Tempo computacional (s) e ganho computacional para grafos randômicos

com 1024, 2048, 3072, 4096 vértices e6|V |arestas. 95

6.3 Tempo computacional (s) e ganho computacional para grafos randômicos

com 5120, 6144, 7168, 8192 vértices e6|V |arestas. 96

6.4 Tempo computacional (s) e ganho computacional com grafos randômicos

de600|V |arestas. 97

6.5 Tempo computacional (s) e ganho computacional com grafos randômicos

de6000|V |arestas. 97

6.6 Tempo computacional (s) e ganho computacional para grafos com 1024, 2048, 3072, 4096 vértices,6|V |arestas e com diâmetro<10. 97

6.7 Tempo computacional (s) e ganho computacional para grafos com 5120, 6144, 7168, 8192 vértices,6|V |arestas e com diâmetro<10. 97

6.8 Tempo computacional (s) e ganho computacional com grafos de |600V |

arestas e com diâmetro<10. 97

6.9 Tempo computacional (s) e ganho computacional com grafos de|6000V |

arestas e com diâmetro<10. 98

6.10 Tempo computacional (s) das implementações paralelas do algoritmo do

Floyd-Warshall 100

6.11 Tempo computacional (s) e ganho computacional para grafos randômicos

com 1024, 2048, 3072, 4096 vértices e6|V |arestas. 103

6.12 Tempo computacional (s) e ganho computacional do algoritmo paralelo de Dijkstra para grafos randômicos com 5120, 6144, 7168, 8192 vértices e

6|V |arestas. 103

6.13 Tempo computacional (s) e ganho computacional do algoritmo paralelo de

Dijkstra com grafos randômicos de600|V |arestas. 103

6.14 Tempo computacional (s) e ganho computacional do algoritmo paralelo de

Dijkstra com grafos randômicos de6000|V |arestas. 103

6.15 Tempo computacional (s) e ganho computacional do algoritmo paralelo de Dijkstra para grafos com 1024, 2048, 3072, 4096 vértices,6|V |arestas e

com diâmetro<10. 105

6.16 Tempo computacional (s) e ganho computacional do algoritmo paralelo de Dijkstra para grafos com 5120, 6144, 7168, 8192 vértices,6|V |arestas e

com diâmetro<10. 105

6.17 Tempo computacional (s) e ganho computacional do algoritmo paralelo de Dijkstra para grafos de|600V |arestas e com diâmetro<10. 105

(17)

6.18 Tempo computacional (s) e ganho computacional do algoritmo paralelo de Dijkstra com grafos de|6000V |arestas e com diâmetro<10. 105

(18)

Lista de Algoritmos

2.1 Algoritmo de Soma de Vetores no modelo PRAM 33

4.1 Algoritmo Sequencial do Warshall 56

4.2 Algoritmo Sequencial BFS 58

4.3 Algoritmo Sequencial BFS para o Problema do Fecho Transitivo 59 4.4 Código CPU - Implementação do Algoritmo BFS proposta por Harish e

Narayanan [16] 60

4.5 Algoritmo em BSP/CGM para Computar o Fecho Transitivo proposto por

Cáceres et al [7] 61

4.6 Algoritmo Sequencial do Floyd-Warshall 63

4.7 Algoritmo Sequencial de Dijsktra 64

4.8 Algoritmo Sequencial de Dijkstra para o Problema APSP 65 4.9 Código CPU - Implementação Paralela do Algoritmo do Floyd-Warshall

proposta por Harish et al. [16]. 67

5.1 Código da CPU - Implementação Paralela Proposta dos Algoritmos do

Warshall e Floyd-Warshall em Blocos 72

5.2 Código da GPU - Implementação Paralela Proposta dos Algoritmos do

Warshall e Floyd-Warshall em Blocos 73

5.3 Função - Cálculo do Fecho Transitivo de um Grafo 73 5.4 Função - Cálculo do Caminho Mínimo Entre Todos os Pares de Vértices

de um Grafo (APSP) 74

5.5 Código da CPU - Implementação Proposta dos Algoritmos Warshall e

Floyd-Warshall 78

5.6 Implementação Paralela Proposta do BFS 83

5.7 Implementação Paralela Proposta do Dijkstra 89

(19)

Lista de Abreviaturas e Siglas

APSP All-Pair Shortest-Path . . . 19

SSSP Single-Source Shortest-Path . . . 19

GPU Graphics Processing Unit . . . 19

API Application Programming Interface . . . 20

CAM Computer Aided Manufacturing . . . 20

CAD Computer Aided Design . . . 20

CPU Central Processing Unit . . . 20

CU DA Compute Unified Device Architecture . . . 20

OpenCL Open Computing Language . . . 20

ALU Arithmetic Logic Unit . . . 24

ILP Instruction-level Parallelism . . . 24

T LP Thread-level Parallelism . . . 24

SIMD Single Instruction Multiple Data . . . 25

SISD Single Instruction Single Data . . . 25

MISD Multiple Instruction Single Data . . . 26

MIMD Multiple Instruction Multiple Data . . . 26

SPMD Single Program, Multiple Data . . . 27

DMMP Distributed Memory, Message Passing . . . 28

DMSV Distributed Memory, Shared Variables . . . 28

GMMP Global Memory, Message Passing . . . 28

GMSV Global Memory, Shared Variables . . . 28

SMP Symmetric Multi-Processing . . . 28

U MA Uniform Memory Access . . . 28

NU MA Non-Uniform Memory Access . . . 29

CC− NUMA Cache Coherence Non-Uniform Memory Access . . . 29

PC Program Counter . . . 31

RAM Random-Access Machine . . . 31

PRAM Parallel Random Acess Machine . . . 32

CREW Concurrent Read Exclusive Write . . . 33

CRCW Concurrent Read Concurrent Write . . . 33

ERCW Exclusive Read Concurrent Write . . . 33

EREW Exclusive Read Exclusive Write . . . 33

MMD Program Counter . . . 34

BSP Bulk Synchronous Parallel . . . 34

CGM Coarse Grained Multicomputer . . . 35

GPGPU General Purpose Graphics Processing Unit . . . 39

SM Multiprocessador de Fluxo . . . 40

SP Processador de Fluxo . . . 40

(20)

FPU Floating Point Unit . . . 41

MAD Multiply Add . . . 41

SFU Special Function Unit . . . 42

BFS Breadth-First Search . . . 55

DFS Depth-First Search . . . 55

FIFO First-In First Out . . . 57

SDSP Single-Destination Shortest-Path . . . 62

(21)

CAPÍTULO

1

Introdução

Problemas relacionados a grafos são geralmente complexos e exigem bastante esforço computacional para sua resolução. O problema do fecho transitivo e o problema do caminho mínimo, quando aplicado a todos os pares de vértices num grafo conexo e dirigido, são exemplos desta dificuldade.

Na Teoria dos Grafos existem alguns algoritmos sequenciais capazes de identi-ficar os caminhos mínimos entre todos os pares de vértices existentes (APSP) e o fecho transitivo num grafo conexo, dirigido. Na literatura, destacam-se dois deles para o pro-blema do caminho mínimo entre todos os pares de vértices: (1) o de Donald B. Johnson, proposto em 1977; e (2) o de Robert Floyd e Stephen Warshall (Floyd-Warshall), pu-blicado em 1962. Um outro método simples de resolver o problema APSP é através de soluções do problema do menor caminho a partir de uma única origem (SSSP), quando aplicados a todos os pares. Exemplo dessa categoria de algoritmos é o algoritmo proposto por Dijkstra (1959). Já em relação ao problema do fecho transitivo, há dois tipos de so-luções: (1) por multiplicação de matrizes, por exemplo, o algoritmo de Warshall (1962); e (2) por buscas, por exemplo, busca em largura e busca em profundidade. Todos pos-suem uma ampla variedade de aplicações em diversas áreas, tais como: bioinformática, roteamento de tráfego em redes, sistemas distribuídos, ou qualquer problema que possa ser representado por um grafo no qual as arestas sejam ponderadas e cujos valores sejam linearmente acumulados à medida que a rede é percorrida [11].

Os algoritmos Warshall e Floyd-Warshall são considerados eficientes em relação ao espaço de armazenamento por possuir uma complexidade de espaço O(|V |2). Entre-tanto, possui uma complexidade de tempo O(|V |3), onde |V | representa o número de vértices do grafo. Devido a essa complexidade cúbica de tempo, trabalhos recentes têm explorado o uso de GPUs (Unidade de Processamento Gráfico ) na tentativa de obter soluções paralelas de melhor desempenho. Dentre eles destacam-se as implementações propostas por Dehne et al. [11], Aini et al. [1], Harish et al. [16], Katz e Kider [20], Ridi et al.[32], Borgwardt et al. [6] e Jian et al. [24]. Por outro lado, acelerado com o heap de Fibonacci, o algoritmo de Dijkstra pode identificar o menor caminho do problema APSP em tempo de O(|V ||E| + |V |2log|V |), onde |E| representa o número de arestas do grafo.

(22)

20

GPUs são microprocessadores dedicados a realizar operações ligadas a aplicati-vos gráficos 2D e 3D. Dentre tais aplicatiaplicati-vos, podem ser citados os de Computer Aided Design(CAD ), Computer Aided Manufacturing (CAM), jogos, interface gráfica com o usuário. Graças à sua arquitetura altamente paralelizada e especializada, são muito mais eficientes na manipulação de gráficos que as Central Processing Units (CPUs ), projeta-das para a execução de código sequencial. Em essência, as GPUs consistem de vários nú-cleos primariamente focados em operações de ponto flutuante, massivamente usados nas funções gráficas dos algoritmos de arte-finalização (ou rendering). A grande quantidade destes microchips, trabalhando em paralelo, é o que permite o alto poder computacional de tais processadores.

A partir do ano 2000, as GPUs incorporaram técnicas de pixel shading, onde cada pixel pode ser processado por um pequeno programa que inclui texturas adicionais, o que é feito de maneira similar com vértices geométricos, antes mesmo de serem pro-jetados na tela. A fabricante de GPUs NV IDIA foi a primeira a produzir placas comR tais características [9]. À medida que as GPUs evoluem, mais flexibilidade de programa-ção é introduzida, adicionando suporte a programas maiores e de maior complexidade (incluindo controladores de fluxo, tais como: laços, sub-rotinas, branches, etc.), mais re-gistradores e aumento na precisão numérica.

Hoje em dia, devido ao altíssimo poder computacional oriundo de uma arqui-tetura intrinsecamente paralela, as GPUs são capazes de manipular enormes cargas de trabalho, com programação flexível e facilitada por meio do uso de diversas Application Programming Interfaces(APIs) disponíveis. Isto têm motivado pesquisadores ao redor do mundo a conceberem algoritmos para execução sob estes equipamentos, usando-as como coprocessadores matemáticos para computação de propósito geral.

De forma correspondente à evolução do hardware, novos modelos de programa-ção capazes de aproveitar o poder desta nova tecnologia têm sido elaborados, destacando-se CUDA (Compute Unified Device Architecture) [9] e OpenCL (Open Computing Lan-guage) [37]. Em ambos, devido a uma ampla disponibilidade de APIs, a implementação de aplicações paralelas eficientes é facilitada, embora as GPUs ainda sejam mais difíceis de serem programadas que as CPUs. Diferentemente da paralelização em CPUs, a or-ganização e o número de threads são gerenciadas manualmente pelo programador. Vale destacar que CUDA foi a primeira arquitetura e interface de programação a permitir que as GPUs pudessem ser usadas para aplicações não gráficas [9].

Este trabalho tem o objetivo de investigar o uso de paralelismo em arquiteturas da GPU para solução de problemas envolvendo grafos, cuja paralelização eficiente é difícil de alcançar. Isso é realizado através de novas soluções paralelas desenvolvidas e analisadas no modelo de programação disponibilizado pela arquitetura da GPU. Soluções baseadas em algoritmos sequenciais que têm a finalidade reduzir o tempo de resolução dos

(23)

21

problemas do fecho transitivo e do caminho mínimo entre todos os pares de vértices. Para isso, essas novas abordagens precisam fazer o uso de propriedades intrínsecas da natureza paralela da arquitetura para obter um desempenho escalável em relação ao tamanho do problema solucionado e independência do modelo do hardware da GPU. Essas soluções são codificadas na linguagem CUDA, e um ambiente de experimentação com diferentes classes de grafos gerados é criado para obtenção de resultados comparativos com os melhores resultados atualmente disponíveis.

O restante deste trabalho está organizado da seguinte maneira: no capítulo 2, são descritos conceitos de computação paralela e medidas de desempenho. O capítulo 3 descreve a arquitetura CUDA da GPU, em nível de hardware e software. O capítulo 4 destina-se à apresentação de conceitos de grafos, algoritmos sequenciais e paralelos que abordaram o mesmo tema. Os detalhes das soluções propostas são descritos no capítulo 5. O capítulo 6 lista as características dos recursos computacionais e os métodos usados durante o processo de experimentaçãos e descreve os resultados obtidos. Por fim, o capítulo 7 apresenta as conclusões do trabalho.

(24)

CAPÍTULO

2

Computação Paralela

A computação paralela envolve o processamento de tarefas em vários processa-dores. Os processadores devem comunicar entre eles usando de alguma forma uma rede de comunicação. Um processador pode ser um simples elemento ou pode envolver um processador superescalar. Já a rede de comunicação pode oferecer um gargalo se ela não suportar comunicações simultâneas entre vários pares de processadores. Assim, a estru-tura de uma máquina paralela envolve considerações sobre diversas características tanto dos processadores quanto da rede de comunicação.

Neste capítulo, são apresentados conceitos essenciais da Computação Paralela. O objetivo é obter uma visão geral da evolução das arquiteturas de computadores paralelos e do processo de modelagem de algoritmos paralelos sobre modelos de máquinas abstratas paralelas. Com isso, uma breve introdução da computação paralela é feita na seção 2.1; algumas classificações de computadores são apresentadas na seção 2.2; modelos compu-tacionais paralelos são na seção 2.3; e por fim, medidas de desempenho de algoritmos paralelos são descritas na seção 2.4.

2.1

Conceitos Preliminares

Em geral, os algoritmos são desenvolvidos de forma sequencial para serem exe-cutados em um único núcleo de processamento. Esses algoritmos sequenciais são partici-onados e ordenados em uma sequência de instruções no momento do processamento. A execução pode ocorrer em máquinas com um ou vários núcleos. Neste último caso, a exe-cução é realizada somente em um, deixando os outros ociosos. Essa maneira de computar algoritmos é conhecida como Computação Sequencial.

Uma outra maneira de computar, menos comum, é execução do algoritmo em paralelo, conhecida como Computação Paralela. A computação paralela explora a concorrência em problemas computacionais. Concorrência é a execução simultânea de tarefas ou dados. Ela ocorre quando os problemas são decompostos em problemas menores nos quais podem ser, seguramente, executados simultaneamente. A fim de explorar essa concorrência, são necessários técnicas e linguagens de programação, nas

(25)

2.1 Conceitos Preliminares 23

quais possibilitam estruturar os subproblemas de tal forma que sejam executados em máquinas adequadas em paralelo, ou seja, máquinas com mais de uma unidade de processamento. Sendo assim, esses algoritmos são conhecidos como algoritmos paralelos [26].

Uma comparação entre execuções de algoritmos paralelos e sequencias são ilus-tradas nas Figuras 2.1 e 2.2, onde uma máquina com um processador e outra máquina com quatro, respectivamente, são usadas. O problema é particionado em pequenos conjuntos de instruções. Entretanto, somente um conjunto de instruções é executado em uma uni-dade do tempo na Figura 2.1. Em contrapartida, a Figura 2.2 apresenta várias instruções sendo executadas em uma mesma unidade do tempo. Essa diferença na execução é devido à quantidade de núcleos de processamento disponíveis na máquina e, principalmente, à estrutura do algoritmo. A quantidade de núcleo depende do modelo computacional do processador.

Figura 2.1: Execução de um algoritmo sequencial em uma Uni-dade de Processamento.

Figura 2.2: Execução de um algoritmo paralelo em várias Unida-des de Processamento.

(26)

2.1 Conceitos Preliminares 24

A grande maioria dos processadores atuais trabalham baseadas no modelo com-putacional desenvolvido por von Neumann por volta de 1946 [8]. Esse modelo consiste em uma memória principal, uma única unidade central de processamento (CPU) e uma rede de interconexão situada entre a CPU e a memória principal [30]. A CPU é composta por uma unidade de controle e uma unidade lógica e aritmética (do inglês, Arithmetic Logic Unit). A unidade de controle é responsável por decidir qual instrução no programa deve ser executada. A ALU é responsável por executar as operações lógico-aritméticas. Os dados, as instruções e as informações sobre o estado de execução do programa são armazenados na memória e são transferidos via a rede de interconexão para a CPU. As máquinas que implementam esse modelo de von Neumann executam uma única instrução em um dado momento, sendo definidas como máquinas sequenciais [36].

A Figura 2.3 ilustra, de forma geral, a arquitetura proposta por von Neumann. Nela, fica visível uma separação entre a memória e o processador, sendo necessária uma conexão. Essa separação é conhecida por produzir um gargalo, uma vez que a rede de interconexão determina a taxa de instruções e dados que poderão ser acessados pela CPU. A fim de resolver esse gargalo e, consequentemente, ganhar desempenho, os fabricantes fizeram várias alterações nessa arquitetura. Algumas delas são: adição de vários níveis de memória cache, paralelismo em nível de instrução (ILP, do inglês Instruction-level Parallelism), paralelismo em nível de thread (TLP, do inglês Thread-level Parallelism).

Figura 2.3: Modelo Computacional de von Neumann

O ILP tenta executar instruções independentes e concorrentes. Para isso, adiciona múltiplos componentes ou unidades funcionais para execução de instruções simultâneas. Ele segue duas abordagens principais: pipeline, na qual várias unidades são arranjadas em estágios, e multiple-issue, na qual várias instruções podem ser executadas simultane-amente por meio de múltiplos e independentes pipelines de instruções [36].

O TLP tenta fornecer paralelismo através de execução simultânea de diferentes threads. Essa técnica fornece um meio de continuar a execução de um programa enquanto

(27)

2.2 Taxonomia de Computadores 25

a tarefa que está sendo executada está parada. Ao invés de procurar paralelismo na execu-ção de uma thread, ele simplesmente pode trocar a thread que está sendo trabalhada [36]. De acordo com Pacheco [30], o multiple-issue e pipeline podem ser considerados paralelismo de hardware, uma vez que as unidades funcionais são replicadas. Contudo, essas formas de paralelismos não são, usualmente, visíveis ao programador. Por isso, podem ser consideradas como extensão do modelo básico de von Neumann.

2.2

Taxonomia de Computadores

Em Computação Paralela, a forma mais frequente de classificar arquiteturas de computadores é pela Taxonomia de Flynn (1966). Ela classifica os computadores de acordo com o número de fluxo de instruções e de dados que um computador pode gerenciar simultaneamente. Essa taxonomia pode ser visualizada na Figura 2.4. Na taxonomia, há quatro possibilidades: SISD, SIMD, MISD e MIMD, detalhadas a seguir.

Figura 2.4: Taxonomia de Flynn

SISD - Único fluxo de instruções, único fluxo de dados (modelo SISD, do inglês Single Instruction, Single Data): uma sequência de instruções é buscada e interpretada por uma unidade de controle; há somente uma unidade de processamento que opera sobre os dados armazenados em uma única memória. Essa categoria apresenta a estrutura mais simples da taxonomia. O modelo clássico de von Neumann, que os tradicionais computadores sequenciais implementam, está incluído nesta categoria. A Figura 2.5 ilustra uma representação das máquinas SISDs na qual há uma entrada de instruções para a unidade de controle e outra de dados para o processador.

SIMD - Único fluxo de instruções, múltiplos fluxo de dados (modelo SIMD, do inglês Single Instruction, Multiple Data): uma unidade de controle que busca e interpreta as instruções e as transmite para uma quantidade de processadores, onde cada qual possui seu próprio fluxo de dados como entrada. A Figura 2.6 ilustra uma representação de uma máquina SIMD com quatro processadores conectados a uma

(28)

2.2 Taxonomia de Computadores 26

Figura 2.5: Categoria SISD

unidade de controle. As aplicações de processamento de sinal digital, imagem (gráfica) e multimídia são normalmente executadas em computadores que se enquadram nesta categoria, por exemplo, as Unidades de Processamento Gráfico (GPU).

Figura 2.6: Categoria SIMD

MISD - Múltiplos fluxos de instruções, único fluxo dado (modelo MISD, do inglês Multiple Instruction, Single Data). Uma sequência de dados é transmitida para vários processadores. Cada processador opera sobre um fluxo de instruções distinto. Os tipo de computadores que podem ser considerados nesta categoria são os computadores sistólicos.

MIMD - Múltiplos fluxos de instruções, múltiplos fluxos de dados (modelo MIMD, do inglês Multiple Instruction, Multiple Data). Cada elemento de processamento tem o seu próprio fluxo de instruções e de dados. Isso faz com que cada unidade de processamento seja completamente independente. A arquitetura MIMD é representada na Figura 2.7, onde uma máquina com quatro processadores conectados por uma unidade de controle diferente. Esta categoria é a mais geral das arquiteturas. Cada uma das outras categorias podem ser representadas por esta [26].

(29)

2.2 Taxonomia de Computadores 27

Figura 2.7: Categoria MIMD

As arquiteturas MIMD e SIMD são consideradas sistemas paralelos. Este traba-lha com paralelismo de dados e aquele trabatraba-lha tanto com paralelismo de dados quanto de instruções. As primeiras máquinas paralelas continham a arquitetura SIMD, por exem-plo, o computador ILLIAC IV que foi o primeiro computador paralelo de grande escala implementado. O interesse inicial nas máquinas SIMD resultou nas características das aplicações e na necessidade da economia. Pela perspectiva das aplicações, a programa-ção em linguagens SIMD era mais fácil e isso leva o hardware SIMD ser mais rentável. Já pelo lado da economia, os processadores totalmente independentes com grande ve-locidade eram muito caros naquela época, limitando, assim, qualquer sistema altamente paralelo diferente da arquitetura SIMD.

Na arquitetura SIMD, há duas formas de execução: síncrono e assíncrono. Quando se recebe uma instrução, na versão síncrona, cada processador pode executar ou ignorar a instrução baseado no estado local ou na condição de dependência. Com isso, existe um tempo de espera, ociosidade, na computação de instruções de condições, deixando ineficientes os processadores em tais computações. Por exemplo, a instrução i f− then − else é executada primeiro habilitando os processadores nos quais a condição é satisfeita e, em seguida, o restante em que a condição else os satisfazem. Isso se torna o pior caso para execução das máquinas SIMD.

Em contrapartida, os processadores na versão assíncrona do SIMD, conhecida como SPMD (Único Programa, Múltiplos Dados), executam sua própria copia do pro-grama. Com isso, a vantagem está em que cada unidade de processamento vai gastar tempo no ramo da condição que lhe é pertinente, ou seja, cada processador pode seguir diferentes caminhos através do programa. Isso traz para o SPMD flexibilidade e cober-tura para as mais importantes estrucober-turas de algoritmos usados computação científica, por exemplo, decomposição geométrica, paralelismo de tarefas e padrões de dividir e con-quistar [26]. Entretanto, uma interação entre várias unidades de processamento e uma alta complexidade em cada processador, tornam-se uma desvantagem para essa classe de

(30)

2.2 Taxonomia de Computadores 28

computadores.

A arquitetura MIMD tornou-se a mais popular recentemente. Isso deve a vários fatores: a flexibilidade de explorar várias formas de paralelismo; em ambientes de mul-tiusuários, há a facilidade de particionamento das tarefas entre os processadores; como cada processador é independente e assíncrono, torna-se mais fácil os algoritmos (progra-mas) serem escaláveis. Essa categoria inclui uma classe ampla de computadores. Com intuito de facilitar o entendimento, o E. E. Johnson propôs, em 1988, uma classificação baseada na estrutura da memória (global ou distribuída) e no mecanismo utilizado para co-municação/sincronização (variáveis compartilhadas ou passagem de mensagem): GMSV, GMMP, DMSV e DMMP. De acordo com essa classificação (Figura 2.8), a classe MIMD em relação a memória pode ser dividida em memória global ou distribuída.

Figura 2.8: Taxonomia de Flynn-Johnson

No sistema de memória global, conhecida, também, por memória compartilhada, todos os processadores compartilham o mesmo espaço de endereçamento, isto é, cada processador consegue ter acesso a todo espaço de memória da arquitetura. A comunicação entre eles é realizada através de leitura/escrita de variáveis compartilhadas ou por envio de mensagens.

A forma mais comum desse sistema é chamada de SMPs (Multiprocessadores Simétricos). Os processadores SMP são semelhantes em suas estruturas e desempenham as mesmas funções. Além de compartilhar o mesmo espaço de endereço, esses processa-dores utilizam a mesma rede de conexão à memória e aos dispositivos de E/S. Sendo as-sim, qualquer processador tem mesmo tempo de acesso a qualquer endereço de memória. Motivo pelo qual essa arquitetura é conhecida por UMA (Acesso Uniforme à Memória). Nesse sistema, usualmente, é mais fácil de programar, pois o programador não precisa se preocupar com diferença de tempo de acesso a diferentes endereços de memória. Tam-bém, não precisa distribuir as estrutura de dados entre os processadores.

A Figura 2.9 ilustra, de forma genérica, uma arquitetura SMP com dois chips semelhantes, cada um tendo dois processadores ou núcleos, compartilhando uma rede de interconexão à uma única memória. Nesta arquitetura, ao aumentar o número de

(31)

2.2 Taxonomia de Computadores 29

processadores, o número de acesso à rede de interconexão à memória também aumenta. Logo, a largura de banda torna-se um fator limitante da arquitetura, ou seja, torna-se um gargalo de desempenho na arquitetura. Assim, os sistemas SMPs não são totalmente escaláveis ao número de processadores.

Figura 2.9: Arquitetura Acesso Uniforme à Memória (UMA)

Um outro modelo de sistema de memória global é chamado de NUMA (Acesso Não-Uniforme à Memória). Como mostra a Figura 2.10, a memória é separada em blo-cos, cada bloco é conectado via barramento a um processador, como memória local. No entanto, há apenas uma única memória endereçável, onde cada posição possui um único endereço no sistema inteiro. Isso faz com que memória continue sendo logicamente com-partilhada, porém, o acesso não é mais uniforme. Esse esquema é conhecido por Arquite-tura de Memória Compartilhada-Distribuída. Os endereços da memória podem ser mais próximos de alguns processadores do que outros. Entretanto, como resultado, o tempo de acesso de um processador a um endereço de memória pode ter diferença significante dependendo de quanto perto está esse endereço do processador. Uma desvantagem dessa arquitetura está na grande quantidade de tráfego às posições de memória não-locais. To-davia, para diminuir esses efeitos, cada processador pode conter um conjunto de memória cache, conhecida por CC-NUMA (NUMA com Coerência de Cache). Apesar de a memó-ria ser compartilhada, as arquiteturas NUMA mais modernas oferecem bibliotecas para programação utilizando troca de mensagens, por exemplo, o computador Cray T3D.

No sistema de memória distribuída, ao contrário da memória compartilhada, cada processador enxerga apenas seu espaço de memória. A comunicação ocorre sobre a rede de interconexão por envio explícito de mensagem ou por funções especiais que fornecem acesso à memória de outro processador. A velocidade de comunicação nesta estrutura depende da tecnologia e da topologia usada na interconexão entre os processadores. Esse sistema é extremamente escalável e permite a construção de projetos altamente paralelos. Os Clusters (sistemas compostos por vários computadores conectados por uma rede) estão incluídos nesta classe. Eles fornecem alto desempenho e disponibilidade. A Figura 2.11

(32)

2.3 Modelos Computacionais Paralelos 30

Figura 2.10: Arquitetura Acesso Não-Uniforme à Memória (NUMA)

ilustra uma representação do sistema de memória distribuída, onde há cinco pares de processador-memória conectados via rede de interconexão.

Figura 2.11: Arquitetura de Acesso Não-Uniforme à Memória (NUMA)

2.3

Modelos Computacionais Paralelos

Nesta seção, são apresentados modelos computacionais paralelos com o objetivo de facilitar o entendimento dos algoritmos paralelos propostos neste trabalho. De acordo com Skillicorn e Talia [35], um modelo computacional paralelo é uma máquina abstrata paralela que fornece abstração e estabilidade do software. Abstração, pois disponibiliza operações a nível mais elevado do que aquelas das arquiteturas subjacentes, o que simplifica a estrutura do software e reduz a dificuldade da sua construção. Estabilidade, porque o software pode assumir uma interface padrão independente da evolução da arquitetura de computadores paralelos.

Uma vez que um modelo é apenas uma máquina abstrata, eles apresentam muitos níveis de abstração. Por consequência, muitos modelos de alto nível podem ser emulados por outros modelos de níveis inferiores. Sendo assim, torna-se difícil a comparação entre eles. Além disso, um modelo abstrato não é de grande interesse

(33)

2.3 Modelos Computacionais Paralelos 31

prático, se um método eficiente não pode ser encontrado para a execução dos programas descritos por ele. Um modelo para ser útil deve abordar duas questões, abstração e eficácia, conforme Skillicorn e Talia [35] e Skillicorn [34]. Não obstante, um bom modelo de computação paralela, segundo esses autores [35] [34], deve ter as seguintes propriedades: facilidade na programação, possuir metodologia de desenvolvimento de software, arquitetura independente, facilidade de entendimento, garantia de desempenho e medidas de custos.

Nesta seção, mostramos vários modelos computacionais paralelas, cada qual com um conjunto de características diferentes. Entre eles, estão: PRAM (2.3.1), Modelos de Rede (2.3.2) e BSP (2.3.3). Porém, antes de apresentar esses modelos, é importante fazer uma breve descrição do modelo computacional das máquinas sequenciais, chamado de Máquina de Acesso Aleatório, com intuito de simplificar a descrição dos modelos paralelos, pois o modelo sequencial é a base dos computadores atuais.

O modelo computacional comumente utilizado para representar computadores sequenciais (classe SISD) é conhecido como Máquina de Acesso Aleatório (RAM, do inglês Random-Access Machine). A RAM é composta principalmente por memória e Unidade de Controle. A memória é formada por uma sequência ilimitada de registradores, cada qual capaz de conter um valor inteiro. O registrador especial, chamado de Contador de Programa (PC, do inglês Program Counter), possui o endereço da próxima instrução que deverá ser executada. Nenhuma hierarquia de memória é mencionada. Já na Unidade de Controle, encontra-se as instruções do programa [14]. Para executá-las, a RAM executa uma instrução do programa a cada ciclo de clock, ou seja, a cada intervalo de tempo. Os dados de entrada do programa são armazenados nos registradores. O tempo de acesso a esses dados, localizados na memória, é constante, ou seja, a complexidade de tempo é de ordem O(1). Uma representação do modelo RAM pode ser visualizado na Figura 2.12, onde os registradores são representados por R0, R1, R2 e o registrador Contador de Programa é representado por PC.

(34)

2.3 Modelos Computacionais Paralelos 32

2.3.1

Parallel Random Acess Machine (PRAM)

Um modelo computacional semelhante ao RAM, entretanto, utilizado para os computadores paralelos, é chamado de Máquina Paralela de Acesso Aleatório (PRAM) proposto por Karp e Ramachandran em 1990 [19]. O PRAM é um modelo básico para análise teórica da computação paralela [35].

De acordo com Goodmane e O’Rourke [14], o modelo PRAM é uma generaliza-ção das máquinas SISD, consequentemente, do modelo RAM. A PRAM é constituída por vários processadores independentes que, geralmente, são vistos como sendo da mesma arquitetura e são conectados por uma memória compartilhada. Esses processadores tra-balham sincronizados em paralelo e utilizam a memória compartilhada para computação e comunicação entre eles. Em consequência de serem RAM, os processadores possuem uma coleção ilimitada de células de memória, como pode ser visto na Figura 2.13. Nessa figura, os processadores RAM são representados por P0, P1, P2 e os registradores por R0, R1, R2.

Figura 2.13: Máquina Paralela de Acesso Aleatório (RAM)

As instruções dos programas correspondentes aos algoritmos representados numa PRAM são carregadas simultaneamente para todos processadores. Entretanto, de-vido à existência de vários processadores RAMs, uma única instrução é executada em uma unidade de tempo em cada processador. Sendo assim, o modelo PRAM fica contido na classe SIMD da classificação de Fynn (seção 2.2).

O algoritmo 2.1 mostra um exemplo de soma de dois vetores no modelo PRAM. A entrada são os vetores A, B e C de tamanho n. A soma de A e B é armazenada no vetor C. O processamento paralelo está no laço da linha 1 que indica que todos os processadores com identificadores entre 0 e n − 1 irão processar a instrução contida na linha 2 em paralelo. Com isso, o número de processadores necessário para a computação do algoritmo 2.1 é da ordem de O(n) e a complexidade de tempo do algoritmo é O(1).

(35)

2.3 Modelos Computacionais Paralelos 33

Algoritmo 2.1: Algoritmo de Soma de Vetores no modelo PRAM Entrada: Vetores A[n], B[n] e C[n].

Saída: Vetor C[n].

1 para todo 0 ≤ id ≤ n − 1 em paralelo faça 2 C[id] = A[id] + B[id];

3 fim

Como há vários processadores trabalhando ao mesmo tempo, a operação de sin-cronização da PRAM pode resultar em acessos para uma mesma localidade na memória compartilhada. Os acessos podem ser de leitura ou escrita de acordo com as variantes do modelo PRAM. Os acessos concorrentes em uma mesma localidade podem ser aceitos ou não. Essas variantes são [34]:

1. Leitura Exclusiva e Escrita Exclusiva (EREW): não permite qualquer tipo de concorrência para a mesma localidade;

2. Leitura Concorrente e Escrita Exclusiva (CREW): aceita somente acessos concor-rentes de leitura. Assim, todos os processadores participantes desses acessos obtém o mesmo valor;

3. Leitura Exclusiva e Escrita Concorrente (ERCW): permite concorrência em acessos somente de escrita;

4. Leitura Concorrente e Escrita Concorrente (CRCW): a concorrência dos dois tipos de acesso à memória (leitura e escrita) é permitido.

Na leitura, um mesmo valor é passado para todos os processadores. Entretanto, há várias formas de tratar a concorrência de escrita. O modo comum permite a concorrência de escrita se e somente se todos os processadores estão tentando escrever o mesmo valor. No modo arbitrário, um valor arbitrário é escolhido para ser armazenado. No modo prioridade, o valor escolhido pertence ao processador com a maior prioridade. Por último, no modo combinação, o valor armazenado é uma combinação dos valores escritos.

O modelo PRAM é um dos principais modelos paralelos atuais, sendo muito utilizado na literatura [34]. Pois, ele despreza qualquer problema da rede de comunicação, deixando o foco somente na computação e, consequentemente, apresenta uma facilidade de aprendizado. Em contrapartida, o modelo PRAM torna-se um modelo muito teórico. Para satisfazer as suas características, seria necessário uma grande área para atender a memória e a rede de comunicação para uma quantidade ilimitada de processadores. Com isso, o modelo PRAM torna-se impraticável sua implementação nos dias atuais.

(36)

2.3 Modelos Computacionais Paralelos 34

2.3.2

Modelos de Rede

A insatisfação relacionada ao tempo de acesso constante a um espaço de ende-reçamento global levou ao desenvolvimento de muitas variantes do modelo PRAM. Uma mudança fundamental de paradigma foi a introdução de modelos em que os módulos de memória são associados aos processadores, em outras palavras, modelos de memória dis-tribuída [25]. Dentre eles, destacam-se: o Modelo de Memória Disdis-tribuída (MMD) [38]; o Modelo Postal (do inglês, Postal Model) [5]; o Modelo Atômico para Passagem de Mensagem [23] e outros.

A maioria dos modelos iniciais após o modelo PRAM ignora a possibilidade de impacto da topologia de rede, ou seja, da estrutura de comunicação de rede. Contudo, a rede de interconexão dos computadores paralelos é o mecanismo que permite todos os processadores comunicarem-se com um aos outros e aos módulos de memória, como descrito na Seção 2.2. Sendo assim, o tipo de topologia é um fator essencial para o desempenho dos computadores paralelos [35]. Visto isso, alguns modelos computacionais paralelos começaram a priorizar o arranjo da topologia de rede de interconexão e, com isso, se destacaram, como é o caso do Modelo de Rede.

Geralmente, o modelo de rede atribui algumas quantidades de memórias locais para cada processador e o custo de acesso a uma memória remota torna-se dependente tanto da topologia quanto do padrão de acesso. Assim, o modelo de rede incentiva proje-tos que sejam eficientes no mapeamento de dados quanto no roteamento da comunicação entre os processadores. Existem, pelo menos, tantos modelos quanto propostas de topolo-gias de rede [25].

Os modelos de rede geralmente são representados por grafos. O desempenho desses modelos envolve três características que são: diâmetro, largura de bisseção e escalabilidade. O diâmetro de uma rede de interconexão é a maior distância entre um par de elementos de processamento. Essa característica tem uma grande influência na latência da rede, pois indica o tempo mais longo necessário para se comunicar entre qualquer par de nós. A largura de bisseção é o menor número de ligações a serem removidas para decompor o grafo em duas partes iguais. Se a largura de bisseção for grande, mais informações podem ser trocadas entre duas metades do grafo de processadores e, assim, os problemas podem ser resolvidos mais rapidamente. A escalabilidade está na facilidade de adicionar mais processadores em um modelo de rede [19] [25] [31] [34] [35].

2.3.3

Bulk Synchronous Parallel (BSP)

Bulk Synchronous Parallel (BSP), proposto por Valiant em 1989, é um modelo que propõe alcançar todo potencial da programação paralela de forma natural e seme-lhante à programação sequencial. A máquina abstrata do BSP consiste em uma coleção

(37)

2.3 Modelos Computacionais Paralelos 35

de processadores abstratos, cada qual com uma memória local, conectados por uma rede de interconexão. A Figura 2.14 representa uma abstração desse modelo.

Figura 2.14: Modelo Bulk Synchronous Parallel (BSP)

O modelo BSP define propriedades da rede de interconexão por alguns parâ-metros arquiteturais. Essas propriedades são: o custo de invocar a sincronização, repre-sentado pela letra L, e o custo necessário para enviar um dado para dentro da rede de comunicação, representado pela letra g. O parâmetro L implica na latência da memória e o g enfatiza a limitação da largura de banda [25]. Esses parâmetros são determinados experimentalmente por cada computador paralelo. Com esse método de abstrair as arqui-teturas paralelas, esse modelo torna-se altamente escalável e facilita a programação em máquinas que implementam esse modelo [35].

Os algoritmos BSP consistem de p threads e de uma sequência de super-etapas, onde p é o número de processadores. Cada superetapa contém: um número de operações de computação, um número de operações de comunicação e, por último, uma barreira de sincronização. No final de cada superetapa, os resultados de comunicações globais tornam-se visíveis na memória local de cada processador logo após a sincronização. A Figura 2.15 ilustra essa superetapa do BSP. Se o tempo de computação é w e o número máximo de mensagens enviadas ou recebidas por um processador é h, então o tempo de uma superetapa é dado por: t = w+hg+L. Com essas características, o BSP é considerado uma generalização do modelo PRAM. Quando a arquitetura BSP tem o valor de g muito pequeno (g = 1), ela torna-se uma PRAM [25] [35].

Um modelo semelhante ao BSP, é o modelo CGM (Coarse Grained Multicom-puter) proposto por Dehne et al [10]. Uma máquina CGM consiste de um conjunto de pprocessadores, cada qual com uma memória de tamanho O(Np). O algoritmo em CGM consiste em uma sequencia de rodadas, onde fases bem definidas de computação local e

(38)

2.4 Medidas de Desempenho 36

Figura 2.15: Super-etapa do BSP: computação, comunicação e sincronização.

comunicação global são realizadas. Normalmente, o tamanho da entrada nos algoritmos CGM é consideravelmente maior do que a quantidade de processadores, por isso o nome coarse grained. O modelo considera somente dois parâmetros: o número de processadores pe o tamanho da entrada N. Tanto a máquina quanto o algoritmo CGM são considerados casos especiais do modelo BSP [10].

2.4

Medidas de Desempenho

A principal razão de implementar um algoritmo paralelo está na obtenção de um desempenho superior ao do algoritmo sequencial. Esse ganho de desempenho (spee-dup) é medido, normalmente, pelo tempo que leva para completar uma tarefa num único processador sobre o tempo necessário para completar a mesma tarefa em N processa-dores paralelos. Assim, o Speedup S(N) é definido usando N processaprocessa-dores paralelos como [30] [31]:

S(N) = Tp(1) Tp(N)

(2-1)

Onde Tp(1) é o tempo do algoritmo sobre um único processador e Tp(N) é o tempo de processamento em processadores paralelos. Uma situação ideal seria a paralelização total do algoritmo. Com isso, teria-se Tp(N) = Tp(1)/N e a equação 2-1 seria transformada em:

(39)

2.4 Medidas de Desempenho 37

Essa situação ideal é díficil de acontecer, pois há vários fatores que influenciam no tempo de execução dos algoritmos paralelos, por exemplo, a sobrecarga de comuni-cação que inclui problemas na rede de intercomunicomuni-cação, largura de banda, colisões na memória, gargalo da memória e entre outros.

Além disso, a lei de Amdahl proposta por Gene Amdahl (1967) [2] dita que todo problema possui uma fração inerentemente sequencial, ou seja, uma parte que não pode ser paralelizada. Com isso, o tempo do algoritmo paralelo depende do tempo da execução dessa fração sequencial. De acordo com a lei de Amdahl [2], o speedup é definido em:

S(N) = 1

s+Np (2-3)

Sendo s o tempo da fração sequencial, p é o tempo da fração do algoritmo que pode ser paralelizada. A Figura 2.16 ilustra um problema para obter o speedup de acordo com Amdahl, equação 2-3. Partindo do pressuposto que o tempo do algoritmo executado em um processador é constante, sendo ele composto por frações sequencial (s) e paralela (p), e a fração paralelizável é fixa; o tempo do algoritmo sendo executado em processador paralelo é a soma do tempo da fração sequencial s somado ao tempo da fração paralelizável p executada em N processadores. Com isso, duas conclusões podem ser tiradas a partir da equação 2-3. Primeiro, quando a fração sequencial s é grande, o uso de processadores paralelos tem pouco efeito. Segundo, quando N se aproxima do infinito, o speedup fica limitado ao inverso da fração sequencial s [2] [15].

Figura 2.16: Lei de Amdahl [15]

Enquanto a lei de Amdahl prediz que o tamanho da fração paralelizável é fixa e não depende do tamanho do problema, a lei de Gustafson (1988) [15] faz a observação de que o paralelismo aumenta em uma aplicação de acordo com o tamanho do problema. Segundo Gustafson, o tempo do algoritmo paralelo é constante e, em contrapartida, o tempo do algoritmo sequencial é variável. Sendo assim, o speedup baseado na lei de Gustafson é definido como:

(40)

2.4 Medidas de Desempenho 38

S(N) = s + N p (2-4)

A Figura 2.17 mostra a divisão de um problema de acordo com Gustafson [15] para obter o speedup. Nela, o tempo do algoritmo em paralelo é constante, considerando isso, quanto maior é o problema, maior poderá ser a quantidade de processadores em paralelo para alcançar um speedup maior. Isso, implica que o speedup não fica limitado pelo tempo de execução da fração sequencial, como dito na lei de Amdahl.

Figura 2.17: Lei de Gustafson [15]

Neste capítulo, foram apresentados os principais conceitos e fundamentos da computação paralela que este trabalho utilizou para alcançar os objetivos: implementa-ções paralelas eficientes para o problema do fecho transitivo e do caminho mínimo entre todos os pares de vértices. No capítulo seguinte, uma explanação sobre a programação paralela nas Unidades de Processamento Gráficas será mostrada.

(41)

CAPÍTULO

3

Unidade de Processamento Gráfico para

Propósito Geral

A GPU (Unidade de Processamento Gráfico, do inglês Graphics Processing U nit) é um coprocessador com uma arquitetura massivamente paralela especializado em computação gráfica. Normalmente, localiza-se em placas gráficas ou, em formas mais simples, integradas no chipsets da placa-mãe.

Inicialmente, a GPU foi desenvolvida com objetivo de processar tarefas gráficas. Embora, nos últimos anos, houve uma grande evolução na computação de aplicações de propósitos gerais. A partir de então, ela ficou conhecida como GPGPU. Isso ocorreu devido ao crescimento computacional do hardware, aliado à flexibilidade de programação e ao suporte de pontos flutuantes 32-bits IEEE adicionados na arquitetura da GPU. Com isso, houve a possibilidade de programar nas GPUs sem conhecimento prévio de linguagens complexas de shaders ou primitivas gráficas.

No presente capítulo, é descrita a estrutura tanto do hardware quanto do software das GPUs da fabricante NVidia. Na seção 3.1, são apresentados conceitos iniciais de paralelismo e Unidades de Processamento Gráfico (GPU). Em seguida na seção 3.2, a arquitetura CUDA é detalhada. Por fim na seção 3.3, o modelo de programação CUDA e suas otimizações são apresentados. Assim, objetiva-se a compreensão detalhada do modelo de programação oferecido por CUDA visando entender as implementações propostas neste trabalho.

3.1

Conceitos Preliminares

A programação paralela baseia-se em processamento simultâneo de várias ta-refas e dados, é nesse ponto onde entra o papel das threads. Uma thread consiste num código de um programa que está sendo executado com valores de variáveis e estrutu-ras de dados. Tanto na CPU quanto na GPU, o processamento da thread é sequencial. Contudo, a CPU é projetada para executar uma pequena quantidade de tarefas bastante complexas. Já a GPU é projetada para o processamento de uma grande quantidade de

(42)

ta-3.2 Hardware CUDA 40

refas simples. Consequentemente, o suporte à thread é muito diferente entre CPU e GPU. Por exemplo, o número de registradores por núcleo é diferente para o suporte de troca de contexto das threads, sendo que a CPU possui um único banco de registradores e a GPU, muitos [8] [40] [21].

A GPU trabalha em conjunto com a CPU, sendo que, aquela muito dependente desta. Pois, os dados inicialmente estão localizados na memória principal do computador, ou seja, na memória RAM da CPU e o recebimento de dados para ser armazenados na memória da GPU é realizado pela CPU. A Figura 3.1 ilustra, de maneira geral, a conexão entre a GPU e a CPU, qual cada tem ligação direta ou alta proximidade com a suas respectivas memórias. Porém, observa-se que a conexão entre elas é realizada por meio do barramento PCI Express. Esse barramento oferece 16 GBytes/s de taxa de transferência de dados, isto na versão 2.0. Já na versão 3.0, este valor chega a ser 32 Gbytes/s. Por simplicidade, a Figura 3.1 omite o chipset que conecta a CPU com outros periféricos, tais como placas de vídeo [40].

Figura 3.1: Arquitetura CPU/GPU simplificada [40].

Entre as soluções de vídeo da NVidia, destaca-se a arquitetura de GPU conhecida como CUDA (Compute Unified Device Architecture). A arquitetura CUDA pode ser divida em duas partes: hardware e so f tware. Quanto à hardware, a arquitetura baseia-se em hierarquia tanto de processadores quanto de memória. Quanto à parte de so f tware, um modelo de programação é oferecido para o programador com intuito de abstrair os detalhes do hardware. Para isso, uma hierarquia de hierarquia de threads é disponibiliza. Nas seções 3.2 e 3.3 é detalhada a arquitetura CUDA tanto à hardware e ao so f tware, respectivamente.

3.2

Hardware CUDA

A arquitetura CUDA contém três características chaves: Processadores de Fluxo (SP, do inglês Streaming Processors), Multiprocessadores de Fluxo (SM, do inglês Streaming Multiprocessors) e uma hierarquia de memória (global, compartilhada e registradores) [8] [40].

(43)

3.2 Hardware CUDA 41

Os Processadores de Fluxo (SP), conhecidos também por CUDA cores, são de-finidos pela NVidia como núcleos de processamentos. Os SPs são constituídos, princi-palmente, por unidades lógica e aritmética (ULA) e unidade de ponto flutuante (FPU). As operações de ponto flutuante são executadas de acordo com o padrão IEEE 754-2008, que fornece suporte à instrução fused multiply-add (FMA) tanto para precisão simples (32 bits) quanto precisão dupla (64 bits). Essa função melhora a instrução MAD (multiplicar-somar) e, é mais precisa do que as operações de adição e multiplicação executadas sepa-radamente. Tais instruções, FMA e MAD, são frequentemente utilizadas em computação gráfica, álgebra linear e em aplicações cientificas. A Figura 3.2 ilustra, de forma simplifi-cada, a arquitetura do SP contendo uma ULA, FPU e registradores.

Figura 3.2: Estrutura do Processador de Fluxo (SP).

Os SPs são agrupados em estruturas que compartilham vários recursos do hardware. Essas organizações são chamadas de Multiprocessadores de Fluxo (SM), onde cada uma suporta uma grande quantidade de threads. Embora todos os SMs não trabalhem sincronizadamente, os SPs contidos em único SM executam no estilo da categoria SIMD, onde processam de maneira síncrona. Por isso que o conjunto de SMs é classificado na categoria MIMD, sendo assim a arquitetura CUDA também o é.

A quantidade de unidades de processamento em todos os SMs em uma GPU va-ria de geração em geração. Conforme Cook [8], essa quantidade é um aspecto chave da arquitetura, pois, dentre outros fatores, permite escalabilidade da GPU. Com essa esca-labilidade, o desempenho do hardware pode ser aumentado simplesmente combinando o número de SMs e o número de CUDA Cores dentro de cada SM. A Figura 3.3 ilustra duas estruturas distintas de SMs. Observa-se a grande diferença de número de SPs entre elas. Em (a), está o SM da arquitetura mais atual, nomeado de SMX, da geração Kepler de CUDA com 192 unidades de processamento; e em (b) está o SM da geração Fermi de CUDA com 32 unidades de processamento. Além dos SPs, os SMs contêm centenas de re-gistradores, memórias internas, escalonadores de warps, Unidades de Funções Especiais (SFU), e entre outros; como ilustrado na Figura 3.3.

(44)

3.2 Hardware CUDA 42

Figura 3.3: Estruturas dos SMs de CUDA, sendo que (a) é a gera-ção Ker pler com 192 SPs e (b) é a geragera-ção Fermi com 32 SPs

O motivo da existência de centenas de registradores dentro dos SMs é para permitir trocas eficientes de contextos entre as threads, com o objetivo de maximizar o desempenho de processamento dos SMs. Isso leva o projeto da GPU a ter solução eficaz sobre a latência contida no acesso à memoria, no qual gasta-se centenas de ciclos de clock para buscar e armazenar dados na memória [40]. Quando um grupo de 32 threads executando em um SM fica ocioso por acessar a memória ou por alcançar uma barreira de sincronização, outro grupo de threads é escalonado instantaneamente para o SM. Isso é realizado pelos escalonadores de war ps contidos nos SMs.

Em relação à memória dentro de todos os SM, há uma divisão entre comparti-lhada e cache. Sendo que esta é utilizada pelo compilador e aquela é manuseada manual-mente pelo desenvolvedor.

Já as estruturas nos SMs chamadas de Unidades de Funções Especiais (SFU) são responsáveis por executar instruções especiais a nível de hardware, como operações seno, cosseno, raiz quadrada e expoente.

A arquitetura CUDA tem alta vazão, pois, dentre outros fatores, há uma hierar-quia de memória disponível para todos os CUDA Cores. Essa hierarhierar-quia faz com que

(45)

3.2 Hardware CUDA 43

as unidades de processamento acessem com menor frequência as memórias com maior latência. Quanto mais alto é o nível da memória nessa hierarquia, maior é a velocidade de acesso. No topo, encontra-se a memória privada dos SPs que é composta por um con-junto de milhares de registradores de tamanho 32-bits localizados dentro dos SMs. Esses registradores, semelhantes aos das CPUs, estão próximos aos SPs e possuem o tempo de acesso muito semelhante ao de processamento dos processadores de fluxo, sendo a mais rápida entre as demais memórias. Isso justifica a sua posição na hierarquia. Além des-sas características, a memória privada pode conter um valor inteiro ou ponto flutuante; e a quantidade varia de acordo com a geração da arquitetura. Por exemplo, a arquitetura Kepler (SM 3.0) contém 65.536 registradores, enquanto a arquitetura Fermi (SM 2.1) possui 32.768 registradores.

Logo abaixo da memória privada, encontra-se a memória compartilhada que, também, localiza-se dentro dos SMs. Porém, diferente da memória privada, ela é aces-sível a todos os SPs contidos num mesmo SM. Cada memória compartilhada é viaces-sível somente a um único SM. Embora usada normalmente para trocar dados entre os SPs do multiprocessador de fluxo, ela pode ser utilizada como memória cache que, diferente da CPU, é inteiramente controlada pelo programador. Com relação à velocidade, a memó-ria compartilhada é 10 vezes menor do que a dos registradores (memómemó-ria privada) [8]. A quantidade de memória compartilhada disponível por SM não é muito, podendo chegar a 64 KBytes na arquitetura Kepler (SM 3.0) e 32 KBytes na Fermi (SM 2.1).

Na base da hierarquia de memória, localiza-se a memória do device que é acessível por todos os SMs e se divide em: global, constante e textura. A global é a principal memória da GPU e aceita operações de escrita e leitura. A memória constante somente aceita operações de leitura. Sendo assim, ela é otimizada para multibroadcast envolvendo múltiplos SPs. Mesmo residindo na memória do device, o acesso à constante é feito por diferentes funções que permitem usar uma cache especial [8]. A memória de textura, por sua vez, é utilizada em dados onde exista interpolação, por exemplo, em 2D ou em 3D com tabelas de consulta. Ela possui uma característica especial que é a interpolação baseada em hardware. Todo SM tem um barramento separado para cada tipo de memória do device, como pode ser visualizado na Figura 3.4. Essa figura mostra uma abstração da estrutura do SM que está conectada com todos os níveis da hierarquia de memória disponibilizados na arquitetura CUDA.

Na próxima seção, é explicado o modelo de programação CUDA disponibilizado para o desenvolvedor. Esse modelo tem como objetivo abstrair os detalhes do hardware, facilitando o desenvolvimento sobre a arquitetura CUDA.

(46)

3.3 Modelo de Programação CUDA 44

Figura 3.4: Arquitetura Geral dos SMs [8].

3.3

Modelo de Programação CUDA

O modelo de programação CUDA é uma extensão da linguagem C e C++, criada em 2006 pela empresa NVidia. Esse projeto teve como objetivo principal permitir o desenvolvimento de aplicações massivamente paralelas e altamente escaláveis nas GPUs. A partir de sua criação, houve uma explosão no desenvolvimento de aplicações sobre plataformas GPUs, principalmente de aplicações não-gráficas. Isso ocorreu devido à facilidade de desenvolver programas com pouco ou nenhum conhecimento em APIs (Interface de Programação de Aplicativos) gráficas . Assim, programas paralelas foram desenvolvidas para diversas áreas, como química, matemática, físicas e atividades gerais de busca e classificação [21].

A abstração dos detalhes da arquitetura é um ponto forte desse modelo, pois torna as aplicações sobre a plataforma da GPU altamente escaláveis e fáceis de desenvolver. Escalável porque o programa fica independente do modelo do hardware que está sendo compilado; e fácil de desenvolver por ter as detalhes do hardware CUDA encapsuladas nas características do modelo programação.

Uma aplicação desenvolvida no modelo de programação CUDA não é totalmente paralelizada, pois requer duas fases: uma sequencial e uma paralela. Elas se alternam entre

Referências

Documentos relacionados

Mesmo com suas ativas participações na luta política, as mulheres militantes carregavam consigo o signo do preconceito existente para com elas por parte não somente dos militares,

Ainda na última parte da narrativa, outro “milagre” acontece: Grenouille apa- rece, de súbito, em meio ao povo, destampa uma pequena garrafa que trazia consi- go, borrifa-se com

As mulheres travam uma história de luta por reconhecimento no decorrer do tempo sistematicamente o gênero masculino dominava o feminino, onde prevalecia a hierarquização do sexo

Como o predomínio de consumo energético está no processo de fabricação dos módulos fotovoltaicos, sendo os impactos da ordem de 50 a 80% nesta etapa (WEISSER,

Diante disto o objetivo deste trabalho foi avaliar a extração de antocianinas a partir do repolho roxo (Brassica oleracea), em diferentes condições de pH, temperatura e

A apixaba- na reduziu o risco de AVE e embolismo sistêmico em mais de 50%: houve 51 eventos entre os pacientes do grupo apixabana versus 113 no grupo do AAS

Tendo como parâmetros para análise dos dados, a comparação entre monta natural (MN) e inseminação artificial (IA) em relação ao número de concepções e

Quadro 5-8 – Custo incremental em função da energia em períodos de ponta (troços comuns) ...50 Quadro 5-9 – Custo incremental em função da energia ...52 Quadro 5-10