Escola de Engenharia
Programa de Pós-Graduação em Engenharia Civil
Porto Alegre
2018
SUMÁRIO
1 INTRODUÇÃO ....................................................................................... 11
2. CUDA ...................................................................................................... 17
69
4. CONCLUSÕES ......................................................................................
REFERÊNCIAS ......................................................................................... 72
ANEXOS ..................................................................................................... 74
LISTA DE FIGURAS
Figura 1.2 – Resultados de Lee et al. (2010) entre o desempenho de CPU x GPU para 14
rotinas usuais em códigos numéricos .........................................................................
Figura 2.5 – Organização local e global dos threads em função do número de threads 22
blocks ..........................................................................................................................
Figura 2.9 – Tempos de execução de parte do código de Nogueira et al. (2015) ........... 27
Figura 2.11 – Performance entre pageable memory e pinned memory em uma Tesla 28
K20 .............................................................................................................................
Figura 2.13 – Trânsito de dados entre os diferentes tipos de memórias do device ......... 32
Figura 2.20 – Speedup para diferentes tipos de precisão para a simulação do problema 41
da cavidade .................................................................................................................
Figura 2.25 – Malha tridimensional elaborada por Senocak et al. (2009) representando 47
parte da cidade de Oklahoma .....................................................................................
Figura 2.26 – Resultados do speedup obtido pelo uso de múltiplas placas gráficas na 48
simulação tridimensional do escoamento em ambiente urbano .................................
Figura 2.27 – Speedup em DNS comparando a performance de CPU versus GPU ....... 49
Figura 2.28 – Divisão de cargas de trabalho para um algoritmo heterogêneo para uso 50
de CPUs e múltiplas GPUs .........................................................................................
Figura 3.2 – Aspecto da malha proposta por Petry (2002) e condições de contorno 61
utilizadas na simulação numérica do problema de escoamento em uma cavidade ....
Figura 3.6 – Comparação entre os dados fornecidos por Ghia et al. (1982) e os obtidos 64
pelas diferentes versões do código de fluidos ............................................................
Figura 3.7 – Malha proposta por Petry (2002) para o problema do escoamento sobre 65
um degrau ...................................................................................................................
Figura 3.11 – Campo de pressões para as diferentes versões do código de fluidos ........ 68
LISTA DE TABELAS
Tabela 3.3 – Performance relativa (speedup) entre os códigos numéricos do Túnel ...... 60
EV – Engenharia do Vento
N-S – Navier-Stokes
PD – Precisão dupla
PS – Precisão simples
TS – Thunder Storms
1 INTRODUÇÃO
A Engenharia do Vento (EV) é um campo de pesquisa que ao longo dos últimos 100 anos
consolidou-se como essencial para o avanço da sociedade, sendo capaz, através de
investigações experimentais e tomada de dados em escala real, analisar e compreender o
comportamento do vento e a interação deste com o homem e suas criações. E a medida que o
arrojo construtivo das edificações e a complexidade dos eventos investigados começaram a
aumentar, o desenvolvimento de mecanismos e métodos capazes de analisar e simular tais
situações se torna imperativo. Nesse sentido, o uso de simulações numéricas na Engenharia do
Vento mostra-se uma alternativa de grande potencialidade, haja vista que atualmente é
possível criar modelos altamente discretizados, e que quando seus resultados são cruzados
com os obtidos em estudos experimentais, obtém-se respostas completas e confiáveis sobre a
ação do vento para uma vasta gama de tipos de estudos na EV. Nesse contexto, vê-se que o
uso de uma abordagem híbrida constitui uma solução consistente e que muitos centros de
pesquisa e autores na literatura vem buscando desenvolver. Todavia, nem sempre é possível
contar com o uso combinado de tais ferramentas, e isto se dá em casos nos quais é necessário
empregar equipamentos muito específicos para a simulação experimental ou para a tomada de
dados em escala real, citando-se como exemplo, a simulação de Thunder Storms (TS) e de
tornados. Para situações tais como as apontadas, tem-se que a simulação computacional
mostrasse um meio alternativo, onde centros de pesquisa com menor capacidade de
capacitação de recursos financeiros e disponibilidade de espaço físico, podem contornar a
falta de equipamentos/recursos, de modo há desenvolver pesquisas relacionadas aos tipos de
eventos exemplificados.
O uso de simulação numérica na EV, teve início por volta da década de 1980, em que
modelos simples buscando simular escoamentos bidimensionais foram confeccionados. E
conforme foram ocorrendo os avanços tecnológicos na área da Computação, possibilitando
maiores capacidades de processamento e quantidades de memória, os modelos numéricos
começaram a se tornar mais robustos e ficando cada vez mais capazes de simular problemas
tridimensionais, apresentando alto grau de refinamento e podendo até mesmo recriar
estruturas complexas que surgem durante o escoamento do vento e a interação deste com
corpos imersos no fluido. Todavia, sempre houverem fatores limitantes no uso de modelos
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
13
Nesse ponto, o nível de discretização dos modelos numéricos pode sofrer grandes avanços e
estudar situações cada vez mais complexas. Inevitavelmente, com o aumento do nível de
discretização, a capacidade de processamento continuou ainda a ser um grande obstáculo,
visto que o tempo demandado para a simulação computacional ainda se mostrava
demasiadamente alto. Destacando-se neste ponto, o trabalho desenvolvido por Alminhana
(2017), que embora tenha utilizado 24 núcleos de processamento para a simulação numérica
de escoamentos de problemas de interesse da Engenharia de Vento, o tempo de
processamento alcançou patamares de 3 a 4 meses de simulação.
O uso de uma abordagem híbrida tal como GPUs + CPU possibilitou a melhora no
desempenho de processamento para diversas aplicações de interesse científico. Em Lee et al.
(2010), encontra-se um estudo cujo propósito foi investigar profundamente a diferença na
performance entre o uso de CPU x GPU+CPU. Os autores estudaram para diversas rotinas
comuns em algoritmos numéricos científicos a performance no processamento, sendo
estabelecido ao final uma relação entre o desempenho CPU x GPU+CPU. Para a investigação,
utilizou-se apenas um processador i7-960 e uma GPU Nvidia GTX 280. Os resultados,
referentes a performance relativa, determinados por Lee et al. (2012) seguem apresentados na
Figura 1.2.
Onde:
SGEMM : rotinas de algebra linear; MC: simulação de Monte Carlo;
Convol: convolução em imagens; FFT: transformada rápida de Fourier para análise de sinais
SAXPY: produtor escalar de vetores; LBM: transformações temporais;
Constraint Solver: mecânica de corpos rígidos; spMV: solver de matrizes esparsas;
GJK: detector de colusão de partículas; Sort: banco de dados;
RC: renderização de volumes; Search: procura em banco de dados;
Hist: análise de imagens; Bilat: análise de imagens.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
15
Pelos dados expostos na Figura 1.3, tem-se que a capacidade de processamento de GPUs
cresceu exponencialmente desde o início dos anos 2000 em relação ao das CPUs. Logo, o uso
desses equipamentos se mostra mais interessante no que concerne a simulação de problemas
computacionais com alta demanda de processamento.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
17
2 CUDA
2.1 INTRODUÇÃO
A computação paralela a muitos anos vem sendo empregada em larga escala em diversos
campos da ciência dado o aumento da complexidade dos problemas investigados e da
demanda de processamento de dados. Nesse sentido, a paralelização de tarefas passou a se
tornar um pré-requisito para qualquer código numérico de simulação computacional. E desde
os primórdios das linguagens paralelas de computação, a atenção era voltada para o
processamento via CPU, e grande parte da comunidade acadêmica utilizava diretrizes de
paralelização de tarefas por sistema de memória compartilhada (OpenMP) ou não (MPI).
Aos poucos os empecilhos quanto ao uso de GPUs foram se esvaindo e o paradigma mudou
completamente quando a empresa fabricante de tecnologia para GPU chamada Nvidia
desenvolveu a arquitetura CUDA para seus processadores. O advento dessa nova arquitetura
foi capaz de contornar as dificuldades ligadas a forma de programação em placas de vídeo
devido a moderna engenharia de computação desenvolvida, que possibilitou a
compatibilização de instruções entre o hardware das GPUs com linguagens de programação
tal como C, usualmente empregada em diversas áreas da ciência. E aos poucos, grandes
centros de pesquisa começaram a trocar o modelo de paralelização de seus programas
computacionais para que passassem a contar com o uso de GPUs. Acrescenta-se que desde
que surgira a arquitetura CUDA, em torno do ano de 2007, teve-se na lista dos 5 maiores
supercomputadores do mundo, 3 utilizando GPUs, e no ano de 2012, o supercomputador mais
rápido do mundo utilizava GPUs para a paralelização de suas atividades. A ascensão da
linguagem CUDA ocorreu devido à sua simplicidade e as poucas alterações na forma de
programar em relação a paralelização de atividades via CPU.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
19
A passagem da carga de cálculos massivos do CPU para o GPU está vinculada a velocidade
de realização de tarefas que a placa gráfica possui. E a explicação para tal fato, está conectada
a arquitetura da GPU/CUDA, que permite que a realização de uma tarefa seja quase que
instantânea, uma vez que a GPU não armazena informações pertinentes ao estado da tarefa
realizada, o que, por exemplo, não ocorre em tarefas feitas empregando CPUs (RUESTCH;
FATICA, 2014). Dados os pontos levantados e discutidos, verifica-se que o avanço em
diversos campos da ciência, que contam com simulações computacionais, está
inevitavelmente vinculado ao uso de algoritmos que utilizem equipamentos com alto nível de
processamento de tarefas em paralelo, cuja aplicação ideal é a de GPUs, visto que as mesmas
foram desenvolvidas para tais situações, possibilitando deste modo, maiores desempenho e
eficiência.
A linguagem de programação CUDA pode ser definida como sendo híbrida, visto o uso
combinado do CPU e GPU. Para fins de uso da potencialidade do CUDA, tanto em linguagem
C ou em Fortran, deve-se sempre ter em mente figuras identificadas como host e device.
Sendo o host composto pelo CPU e sua memória, ao passo que a GPU e sua memória
compõem o device.
Na utilização da arquitetura CUDA, o código principal sempre é controlado pelo host, e cabe
a este destinar e gerenciar as tarefas a serem realizadas por ele próprio e pelas que ficarão a
cargo do device. Em suma, tem-se que tarefas massivas de computação serão direcionadas do
CPU para a GPU, ao passo que o CPU será o responsável pelo trânsito, andamento e troca de
Neste ponto, cabe salientar como funciona a organização do device, na arquitetura CUDA,
para a realização de uma rotina. Inicialmente, uma tarefa (thread) a ser realizada na GPU é
feita por um core ou thread processor, sendo que um conjunto de thread processors constitui
o que se chama de multiprocessor. Um multiprocessor, por definição, é composto por
unidades de processamento e uma memória compartilhada. O conjunto composto por todos os
multiprocessors formam a GPU. Ao final, tem-se que a GPU, juntamente, com memória
DRAM, configuram o device. Abaixo segue a Figura 2.1 que evidencia cada unidade
componente do device.
2.2.1 Kernels
Para que o device realize uma tarefa específica, demandada pelo host, é necessário estruturar
o que se identifica por kernel, sendo esta parte do código entendida como um conjunto de
instruções que serão realizadas somente pela GPU. Sendo assim, toda tarefa do código que
demande processamento via GPU deve utilizar kernels, que deverão conter todas as instruções
referentes a realização das instruções almejadas. Os kernels somente são processados quando
acionados por comandos específicos e usualmente são escritos no formato de sub-rotinas.
Contudo, em alguns casos, pode-se escrevê-las ao longo das instruções do código do host.
Para que um kernel seja lançado, deve-se ao longo do corpo principal do algoritmo
computacional (código no host), inserir comandos de acionamento. Abaixo, mostra-se uma
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
21
forma de acionar um kernel em CUDA Fortran, sendo que as tarefas a serem executadas pelo
mesmo se encontram descritas em uma sub-rotina específica.
Na Figura 2.2, a segunda linha específica que a sub-rotina “incremento” será realizada pelo
device. No trecho composto por “<<<grid, tPB>>>”, tem-se que as variáveis “grid” e “tPB”
especificam o tamanho da malha de blocos de processamento (thread blocks) e o número de
unidades de processamento (threads) por thread block que a GPU deverá utilizar para
completar a tarefa lançada pelo host. Destaca-se que thread blocks podem ser uni, bi ou
tridimensionais conforme é especificado no seu acionamento. Para invocar kernels que
utilizem duas ou mais dimensões para os blocos de processamento, pode-se utilizar o
comando “dim3”, tal como é apresentado abaixo, na Figura 2.3.
Destaca-se que é necessário que a execução de thread blocks seja independente entre eles
próprios. Podendo-se entranto, dentro de um mesmo thread block a troca de informações entre
os threads locais.
Na linha 1 da Figura 2.2, tem-se que a instrução “a_d = a” é responsável pela transferência de
dados de informações que constam na variável “a”, que está alocada no host, para “a_d”, que
está situada na memória do device. Já na linha 3, tem-se a transmissão das informações entre a
variável “a_d”, que fora utilizada no device, e “a”, que foi atualizada em função dos valores
determinados para “a_d” após a execução do kernel “incremento”.
Um kernel construído como uma sub-rotina apresenta como estrutura padrão as diretrizes
expostas na Figura 2.4:
Na Figura 2.4, tem-se que toda sub-rotina que lança um kernel deve fazer parte de um module,
que no caso do exemplo é chamado de “cudakernels”. Além disto, tem-se que a diretriz
attributes(global) indica que o presente kernel, intitulado “incremento” é chamado do host
para ser executado no device. Caso fosse especificada a diretriz attributes(local), teria-se que
o kernel será chamado no device e nele próprio será executado. E por fim, tem-se a
possibilidade de declarar-se um módulo como sendo attributes(host), o qual será executado
apenas pelo host. Após, tem-se a declaração das variáveis que integram o kernel, e aqui
destaca-se o atributo value, que tem como função buscar na memória do host o valor de uma
variável escalar atribuída ao longo do código. Por fim, tem-se na variável “i” a identificação
de todos os threads lançados para a realização das tarefas contidas no kernel “incremento”.
Nesse ponto, se faz necessário compreender como é feita a ordenação dos threads em função
do número de thread blocks lançados na chamada do kernel.
Tomando como ponto de partida um kernel genérico lançado com o seguinte padrão
“<<<4,4>>>”, tem-se a formação de 4 thread blocks, sendo executados 4 threads por bloco.
Logo, ao todo, serão executados 16 threads. Do ponto de vista do device, os threads são
numerados de forma local, ou seja, por thread block, sendo assim, não possuem uma
numeração global. Dessa forma, deve-se utilizar um mecanismo que ajuste a numeração dos
threads, de termos locais para globais. E para isso, recomenda-se o uso da instrução
apresentada na linha 9 da Figura 2.3. Para uma melhor compreensão do sistema de
identificação dos threads, dentro dos threads blocks e também do que efeito gerado pela
instrução da linha 9, tem-se a Figura 2.5, que demonstra a identificação local e global para os
threads lançados para a realização das tarefas do kernel genérico tomado como exemplo.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
23
Caso se deseje fazer a chamada de kernels, de uma outra forma, sem utilizar sub-rotinas,
pode-se empregar o que se chama de kernel loop directive (KLD). A abordagem KLD, além
de ser uma forma alternativa para o chamamento de kernels, é ideal para situações as quais
são exigidos o uso de loops (comandos que contam com o uso de instruções do tipo do) (PGI
Compiler & Tools, 2017). A escrita das instruções mostradas na Figura 2.3, utilizando a
abordagem KLD, pode ser realizada utilizando o seguinte padrão de instruções:
a) Se a diretiva especifica “n” dimensões, ela deve ser seguida por pelo menos
loops de “do” fechados;
b) Os loops “do” devem ter limites invariáveis: o limite inferior, limite superior, e
incremento invariável com relação a qualquer outro loop dentro do mesmo
kernel;
c) Não pode haver instruções “goto” ou “exit” dentro ou entre quaisquer loops
que tenham sido mapeados empregando valores de configuração da grid e do
bloco de processamento;
d) O corpo dos loops pode conter instruções de atribuição, instruções “if”, loops e
declarações “goto”;
i) Loops implícitos e a sintaxe de array F90 não são permitidos nos KLDs;
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
25
lançados. Para isto, deve-se ao declarar as variáveis do device colocando o atributo device, tal
como é mostrado abaixo na linha 23 da Figura 2.7.
Todas as variáveis declaradas com o atributo device são alocadas na memória global do
device. As variáveis que podem ser empregadas em códigos, contando com recursos do
CUDA, podem possuir diferentes tipos de qualificações. Conforme PGI Compiler & Tools
(2017), os principais tipos são:
a) device: este tipo de qualificação para uma variável faz com que se a declaração
seja feita no código do host, que possa ser utilizada em sub-rotinas que a
contenham. Já se for alocada em um módulo que contenha sub-rotinas de
kernels, esta variável pode ser utilizada em qualquer kernel do módulo.
b) managed: este tipo de qualificação só pode ser feita a partir do CUDA 6.0.
Como principal característica, tem-se que variáveis declaradas com este
atributo podem ser utilizadas tanto no host quanto no device.
f) texture: são utilizadas apenas no device, devendo ser ponteiros do tipo real ou
inteiro. Podem causar melhoras no desempenho do código.
Observando a Figura 2.8, verifica-se que mesmo em capacidades de computação altas, tais
como as 2.0 e 3.5, o número de unidades de cálculo para precisão dupla é relativamente baixo,
atingindo ao máximo 832 núcleos na GPU Tesla K20, ao passo que para a precisão simples
tem-se 2496 núcleos, o que resulta em uma performance de 3524,35 GFLOPS para PS e de
1174,78 GFLOPS para PD. Destaca-se que GFLOPS denota que a quantidade de operações de
ponto flutuante que o equipamento é capaz de realizar por segundo, sendo este parâmetro uma
forma de medir o desempenho de processamento. De forma resumida, vê-se que o melhor uso
e maior desempenho são obtidos, ao se utilizar GPUs, na declaração de variáveis empregando
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
27
PS, onde verifica-se uma performance muito superior em relação ao uso de CPUs para
atividades similares.
Tem-se que a taxa de transferência, entre host e device, ocorre pela placa-mãe em seu
barramento PCI-Express, sendo limitada pela tecnologia desta. Sendo assim, caso a placa
tenha conexões de 2° ou de 3° geração, a capacidade de troca de informações será,
respectivamente, 8 GB/s e 16 GB/s. No entanto, a transmissão de informações entre a
memória do device e a GPU é capaz de atingir taxas na faixa de 200 a 400GB/s, o que indica
que a transmissão de dados entre host e device deve ser evitada sempre que possível, pois ela
sempre resultará em gargalos de desempenho no código computacional (RUESTCH;
FATICA, 2014). Neste sentido, para melhor compreender que a transmissão entre dados deve
ser evitada sempre que possível, cita-se o trabalho de Nogueira et al. (2015), que investigou
os tempos de execução de dadas tarefas para um algoritmo numérico que utilizava os recursos
de processamento da GPU. Nogueira et al. (2015) percebeu, para seu código, que o tempo de
transferência de dados foi maior do que o tempo de execução da rotina principal do código, tal
como é mostrado na Figura 2.9.
device. A pageable memory trata-se de um tipo de memória que pode ser apagada e
sobreescrita ao longo da execução de um algoritmo computacional, ao passo que a pinned
memory uma vez preenchida por informações não pode ser apagada ao longo da execução do
código. Além disso, tem-se que toda variável declarada como sendo do tipo pageable
necessitará da criação de um buffer temporário de memória do tipo pinned para que assim se
faça a transmissão de informações entre host e device (RUESTCH; FATICA, 2014). A seguir
demonstra-se, na Figura 2.10, a diferença que existe na transmissão de informações entre a
memória do tipo pageable em relação a do tipo pinned.
Sendo assim, a alocação de variáveis empregando um dos dois tipos de memória implicará,
inevitavelmente, em diferentes níveis de performance, uma vez que em um deles a
transmissão é feita diretamente e o outro necessita de um passo intermediário para tal.
Contudo, deve-se ficar atento que a alocação de variáveis do tipo pinned pode levar ao
esgotamento da memória RAM do host rapidamente, pois uma vez alocada uma variável neste
tipo de memória, a mesma só voltará a estar disponível após a finalização do código numérico
ou através da desalocação da variável. Nesse sentido, o uso exacerbado de variáveis do tipo
pinned pode afetar tanto o desempenho do código quanto o do funcionamento do computador.
Logo, seu uso deve ser feito com ponderação (SANDERS; KANDROT, 2010).
Em Ruestch e Fatica (2014) é escrito um código que avalia a transferência de dados entre host
e device empregando para isto as memórias do tipo pageable e pinned. Os autores utilizaram
uma placa Tesla K20 para a realização dos testes e chegaram ao seguinte resultado:
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
29
Analisando a Figura 2.11, verifica-se que o desempenho na transmissão de dados entre host e
device é 3,46x maior utilizando a memória do tipo pinned diretamente, ao passo que a
transmissão inversa, ou seja, device para host, é 4,12x mais rápida. Utilizando o código
proposto por Ruestch e Fatica (2014) e trocando a GPU por uma Nvidia GTX 750Ti, obtêm-
se os seguintes resultados:
Para a GTX 750Ti já se verifica que o desempenho na transmissão de dados apresenta uma
diferença menor na performance entre a pageable memory e pinned memory. Nesse sentido,
tem-se que para esta placa de vídeo, o uso de pinned memory pode ser reduzido em relação ao
do que seria para a Tesla K20.
Para aproveitar ao máximo a potencialidade e nível de paralelização das GPUs modernas, faz-
se necessário o conhecimento dos diversos tipos de memórias presente no dispositivo.
Segundo Ruestch e Fatica (2014), tem-se que as memórias que a GPU possui são separadas
em:
Observando o Quadro 1, verifica-se diferentes tipos de memória, sendo que cada uma possui
distintas características em relação à outra. Abaixo são apresentadas as principais
peculiaridades das mesmas:
a) Global: é um tipo de memória no qual, tanto o host quanto o device, podem ler
e escrever dados. A sua duração se dá por aplicação e a informação fica
disponível para todos os threads lançados e pelo host, ela fica localizada na
DRAM;
e) Texture: pode ser entendida como sendo similar à memória constant. Constitui
uma alternativa para se evitar o uso da memória global, que é mais lenta na
ordem de 100x.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
31
1
Bandwidth: pode ser entendido como a taxa de transferência de dados.
2
Latency: tempo entre o acionamento de um processo lançado e a percepção de o efeito deste.
investigação realizada pelos presentes autores será comentada em maior detalhe mais à frente,
no item 2.3.1.
Em Couto (2016), encontra-se uma ilustração que demonstra o fluxo de informações entre as
diferentes memórias presentes na GPU.
Sobre o uso da memória do device, Ruestch e Fatica (2014), comentam que o aspecto mais
importante a se considerar na performance de um algoritmo em CUDA seria o acesso à
memória global de forma corrida, isto é, de forma sequencial sem intervalos.
Para melhor compreender o que seria o acesso corrido a memória, faz-se necessário definir o
que se chama de warp. Segundo o manual da PGI Compiler & Tools (2017), toda instrução
executada pelo device é feita a partir de grupos de 32 threads, denominados warps. Sendo
assim, tem-se que a memória global do device é lida e escrita de forma corrida em termos de
um warp para capacidade computacionais acima de 2.0, e de meio-warp para inferiores
(RUESTCH; FATICA, 2014).
O acesso a memória global modifica diretamente a performance do algoritmo, tanto que sobre
este aspecto, Ruestch e Fatica (2014) investigaram o impacto no bandwidth de um código em
função do tipo de deslocamento (offset: pulo na sequência de dados ou strided: múltiplo de
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
33
contador de acesso) na leitura à memória global do device para diversas GPUs. Os resultados
obtidos seguem apresentados nas Figura 2.14 e 2.15.
Analisando os resultados ilustrados nas Figuras 2.14 e 2.15, conclui-se que a melhor
performance obtida, na situação de um offset no acesso à memória global, ocorre quando não
há deslocamento, ou quando o deslocamento é de meio ou um warp completo. Ainda se tem a
possibilidade de acesso via strided, deve ser evitada sempre que possível, uma vez que afeta
em demasia o bandwidth do algoritmo.
Contudo, nem sempre é possível evitar de utilizar acessos à memória global que usem
alternativas que resultem em uma melhor performance. Para essas situações, uma forma para
amenizar o impacto na performance, é buscar empregar tipos de memória que possuam
penalidades menores, tal como as do tipo texture ou shared. Todavia, acessos do tipo strided
sempre irão resultar em baixa performance no tráfego de informações.
3
Occupancy: taxa que relaciona o número de warps ativos e o número máximo de warps presentes na GPU.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
35
paralelismo pode ser empregado também em KLDs, para maiores detalhes ver Ruestch e
Fatica (2014).
A comunicação peer-to-peer ocorre sempre que uma GPU necessita de informações que estão
contidas em uma outra. Logo, para que uma acesse a informação da outra, é necessário
empregar um mecanismo de comunicação que faça a conexão entre as memórias de ambas, e
uma vez feito tal procedimento, a transmissão de dados entre GPUs pode ser realizada tal
como:
a1_d = a0_d
Onde:
a0_d: variável com as informações necessárias para o funcionamento da GPU tida como 1.
Como coloca Ruestch e Fatica (2014), na versão CUDA 4.0 foi introduzido um mecanismo
chamado de Unified Virtual Addressing (UVA), que é responsável por agrupar as memórias
do host e das múltiplas GPUs de um sistema em uma única memória virtual. Sendo assim, o
acesso de informações nesse sistema virtual formado, facilita o trânsito de dados entre os
dispositivos, uma vez que se pode fazer acessos diretamente aos dados (direct acess) ou
também por transferências diretas (direct transfers) entre GPUs. Para melhor compreender-se
esse conceito, abaixo segue demonstrado na Figura 2.17, como são feitos ambos os
procedimentos de troca de informações.
O uso de MPI para a implementação do uso de múltiplas GPUs, passa, inicialmente, pela
geração de nós (ranks), que podem ser entendidos como threads a serem executados. Sendo
assim, pode-se através dos diferentes nós gerados, dividir a carga de trabalho por GPU e
assim aumentar o nível de paralelismo de execução do algoritmo numérico. Observando
alguns trabalhos utilizando o uso de MPI e CUDA, verifica-se que a performance de execução
pode atingir patamares de 2x a 3x dos algoritmos em relação ao uso de apenas uma GPU,
como por exemplo é mostrado nos trabalhos de Thibault e Senocak (2012) e Senocak et al.
(2009), maiores detalhes sobre estes estudos serão discutidos mais à frente no texto.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
37
A partir das funções apresentadas no Quadro 3, pode-se fazer uso de funções atômicas ao
longo do código do device. Contudo, deve-se ficar atento com o uso excessivo dessas funções,
uma vez que o desempenho do código numérico pode ser afetado substancialmente a medida
que elas sejam empregadas em demasia.
pela GPUs em simulações computacionais. Sendo assim, neste item serão demonstrados
alguns trabalhos cujo objetivo era elucidar o comparativo de desempenho entre o uso de
CPUs e GPUs para simulações numéricas, particularizando para problemas relacionados à
Dinâmica dos Fluidos e Engenharia do Vento.
Dentre os principais pontos levantados por Thibault e Senocak (2012), tem-se que para a
implementação de um algoritmo numérico empregando a arquitetura CUDA, o programador
deve ter total conhecimento do gerenciamento do trânsito de informações, não somente entre
host e device, mas como também entre as diferentes memórias que o device dispõe. E neste
ponto, destaca-se os resultados obtidos pelos autores citados, no tocante a performance entre o
uso da memória global frente à memória compartilhada (shared) da GPU. Abaixo, segue a
Figura 2.18, em que são apontados os resultados obtidos pelos autores quanto ao tipo de
memória empregada durante a execução de kernels em seu código numérico para a solução
das equações de Navier-Stokes para fluidos incompressíveis.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
39
A análise realizada por Thibault e Senocak (2012), cujos resultados são apresentados na
Figura 2.17, buscou-se determinar a performance relativa (speedup) entre o uso da memória
global e compartilhada, tomando como referência os resultados obtidos para o uso da
memória global. Analisando os dados apresentados, verifica-se que para dois dos três kernels
do código numérico dos autores, o uso de memória compartilhada culminou em uma melhor
performance, haja vista que ambas resultaram em speedups superiores a 1,0. Contudo, em um
dos kernels não se verificou melhora pela utilização de memória compartilhada, o que indica
que possivelmente, nesta parte do código, o trânsito de informações entre a memória global-
compartilhada-global demandou mais tempo do que apenas os cálculos a serem realizados
pela GPU. Sendo assim, pode-se dizer que o uso de memória compartilhada deve ser
investigado caso a caso, uma vez que pode afetar significativamente a performance do código
numérico. No final da investigação sobre os tipos de memória, Thibault e Senocak (2012)
apontam que o uso misto de tipos de memória entrega uma melhor performance no algoritmo
computacional, porém, a complexidade de programação é ampliada neste tipo de abordagem,
devendo o programador avaliar se deverá utilizar um código com maior ou menor
performance, dada a dificuldade de programação que irá se ter ao elaborar tal algoritmo.
Analisando os resultados para o speedup no problema da cavidade, pode-se notar que o uso de
GPUs para o processamento computacional do código elaborado pelos autores foi, no
mínimo, 10,8x mais rápido do que o utilizando um único CPU. Além disto, tem-se que o uso
de múltiplas de GPUs é capaz de aumentar substancialmente a velocidade de processamento
do problema. Sendo assim, é interessante utilizar, quando possível, mais de uma GPU para
estudar problemas mais complexos e com maior nível de discretização, obtendo as respostas
mais rapidamente. Acrescenta-se a análise de resultados, que em relação ao melhor speedup
entre os CPUs empregados frente ao melhor obtido pela GPUs, nota-se uma diferença de
12,2x. Portanto, o uso de múltiplos núcleos de processamento em um CPU, para o hardware
utilizado pelos pesquisadores, não se mostra suficientemente eficaz para simulação de
problemas de Dinâmica dos Fluidos, sendo preferível, a utilização de GPUs.
Para elucidar a diferença em termos de processamento entre o uso de precisão simples e dupla
em GPUs, os pesquisadores simularam o problema da cavidade utilizando variáveis em ambos
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
41
os tipos de precisão. Os resultados determinados por eles indicaram que o cálculo para
precisão dupla é aproximadamente 2,5x mais lento do que o empregando precisão simples.
Logo, o programador deve pesar se é benéfico, ou não, a declaração de variáveis utilizando
precisão dupla para a simulação do problema almejado. Abaixo, na Figura 2.20, são
apresentados os resultados obtidos pelos pesquisadores.
Salienta-se que a referência para o cálculo do speedup foi estabelecida como sendo a
performance alcançada pelo Intel Core 2 Duo.
No trabalho realizado por Couto (2016) foi elaborado um algoritmo computacional, baseado
em linguagem C, voltado para a aplicação da arquitetura CUDA, para a solução de problemas
de Dinâmica dos Fluidos. No referido trabalho, analisou-se problemas típicos da área e que
seguem descritos abaixo:
a) Escoamento em cavidade;
Para a simulação computacional dos problemas listados acima, o autor utilizou os seguintes
equipamentos:
Memória RAM: 16 GB
Para este problema, o autor elaborou diversas malhas em elementos finitos, com as mesmas
apresentando de 98 a 40920 elementos. Desta forma, ele buscou determinar o tempo de
processamento do problema da cavidade em função do tamanho da malha.
Após a simulação numérica, o autor avaliou os tempos relativos ao tratamento das matrizes
fundamentais do modelo numérico e o de simulação total. Os resultados obtidos pelo autor
seguem apresentados na Figura 2.21.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
43
Conforme ilustrado na Figura 2.21, verifica-se que para as menores malhas, o desempenho
entre CPU e GPU ficam em patamares próximos. Contudo, à medida que a malha em
Elementos Finitos é refinada, percebe-se que a GPU começa a apresentar desempenhos muito
superiores em relação aos do código na versão serial de CPU. Tal resultado entra em
consonância com os apresentados em Thibault e Senocak (2012) que apontam que o
processamento paralelo em GPU só é interessante quando há uma grande demanda de
atividades em paralelo, para que assim se consiga contrabalancear o tempo de transmissão das
informações do host para o device. Ainda sobre os resultados de Couto (2016), verifica-se
speedups da ordem de 4x em relação ao código serial, mostrando assim a potencialidade do
uso de GPU para o tratamento de problemas de Dinâmica dos Fluidos. Por fim, os resultados
encontrados pelo autor, tanto em seu código serial quanto paralelo mostraram grande
concordância com os obtidos por Ghia et al. (1982) para o problema da cavidade com o
número de Reynolds (Re) igual a 400.
Finalizada a simulação numérica, o autor obteve os resultados apontados na Figura 2.22, para
os tempos relativos ao tratamento das matrizes fundamentais do modelo numérico e de
simulação total. Os resultados obtidos, pertinentes ao problema de Dinâmica dos Fluidos,
foram comparados com os obtidos por Gomes (2013) e Shaffer et al. (1996), indicando boa
concordância.
Verifica-se, através da Figura 2.22, que o speedup para o código numérico paralelizado via
GPU atingiu patamares de 10x e 4x, respectivamente, para o tratamento das matrizes
principais e tempo total de simulação. Sendo assim, percebe-se que a medida que
refinamentos fossem necessários na malha de elementos finitos, o código paralelo forneceria a
possibilidade de maior discretização e entrega de resultados em menor tempo do que o código
sequencial sem refinamentos até uma razão aproximada de 4x. Conclui-se, portanto, que para
problemas que necessitem de altos números de Reynolds, consequentemente, maiores
refinamentos, os resultados obtidos por Couto (2016) sugeririam o uso de códigos paralelos
utilizando os recursos de processamento de GPUs.
Phillips et al. (2009), visando avaliar a potencialidade do uso de GPUs via arquitetura CUDA,
desenvolveram um código computacional capaz de resolver as equações de Euler para fluidos
compressíveis. No trabalho desenvolvido pelos autores, foi simulado o escoamento de um
fluido compressível através de um estreitamento, tal como mostrado na Figura 2.23, que
segue abaixo.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
45
Para o mesmo problema apontado na Figura 2.23, os autores utilizaram tanto CPUs quanto
GPUs, buscando compreender as diferenças na performance entre as distintas formas de
processamento empregadas. O critério estabelecido como meio de medida foi o número total
de pontos por malha lidas pelo tempo de uma única interação do código desenvolvido. Os
resultados obtidos por Phillips et al. (2009) podem ser vistos nas Figura 2.24, que aponta os
resultados para os casos de CPU/GPU únicos e clusters, respectivamente.
Conforme ilustrado na Figura 2.24a, nota-se que para o caso de simulação empregando apenas
CPU ou GPU únicos, o desempenho no dimensionamento variou em função do tamanho da
malha empregada para a solução do problema. Nesse sentido, pode-se ver que para a menor
malha investigada por Phillips et al. (2009), que quando a demanda computacional por
processamento é baixa, o tempo de transferência de dados é demasiadamente alto em relação
ao de processamento, fazendo com que o speedup seja pequeno em relação ao das demais
malhas empregadas, ficando este na faixa de 3,5x em relação aos dos CPUs. No entanto, à
medida que a demanda de processamento se torna alta, compensando assim o fluxo de dados,
o desempenho ofertado pela GPU torna-se muito mais vantajoso, podendo oferecer speedups
da ordem de 20x em relação ao dos CPUs. Já na Figura 2.24b, ao utilizar múltiplas GPUs, os
autores verificaram ser possível atingir um patamar de speedup da ordem de 496x em relação
ao uso de apenas um núcleo do CPU Core 2 Duo, mostrando assim a potencialidade da GPU
para o processamento paralelo de dados.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
47
Os resultados obtidos pelos autores demonstraram que o speedup obtido, utilizando placas
gráficas, para o processamento paralelo pode atingir patamares de 100x quando comparados
com o mesmo problema simulado por apenas um CPU. Nesse sentido, percebe-se que o uso
de GPUs possibilitaria em uma situação de evento extremo, uma resposta em um tempo 100x
menor do que via processamento via CPU. Logo, nota-se que em problemas, cuja natureza
implica em uma alta demanda processamento paralelo, os mesmos passarão a ser
confeccionados utilizando linguagens e diretrizes que permitam o uso de placas gráficas, dada
a performance de processamento que se atingir. Abaixo seguem, na Figura 2.26, os resultados
referentes ao speedup obtido pelos autores.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
49
efeitos. Sendo assim, sempre foi de interesse da Engenharia do Vento desenvolver simulações
numéricas diretas, empregando diretamente às equações de Navier-Stokes.
Analisando a Figura 2.27, onde são mostrados os resultados para o speedup para as diferentes
malhas utilizadas pelos autores, variando de 100.000 a 400.000 nós. Na figura, vê-se que o
menor speedup obtido no estudo realizado pelos autores foi superior a 6x para a GPU,
podendo o speedup máximo alcançar patamares superiores a 12x para um nível maior de
discretização. Sendo assim, nota-se que mesmo para a simulação computacional de problemas
de escoamento via DNS, as placas gráficas são capazes de permitir uma grande melhora na
performance de cálculo e assim, reduzir os tempos associados ao processamento numérico do
problema, que conforme os resultados apresentados por Oyarzun et al. (2013) pode ser na
ordem de 6x menor.
Em Lai et al. (2017) foi realizado um estudo acerca do uso de um algoritmo numérico que
utiliza os recursos de processamento de GPU e CPU, através da arquitetura CUDA, para a
solução das equações de Navier-Stokes. No referido trabalho, empregou-se um esquema
heterogêneo para a distribuição de tarefas do código, tal como o apresentado na Figura 2.28.
Para o trabalho, os autores utilizaram um CPU Intel Xeon 2670 e uma GPU Nvidia 1070
como hardware e estudaram problemas clássicos da Dinâmica dos Fluidos, voltada para a área
da aeronáutica, tal como: elipsoide duplo, escoamento em torno de um jato e escoamento
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
51
3 ESTUDO DE APLICAÇÃO
Neste item serão apresentadas algumas comparações feitas entre o desempenho de diferentes
códigos e formas de paralelização dos mesmos. Para isto, serão simulados diferentes
algoritmos computacionais vislumbrando comparar o tempo de execução entre o uso de
apenas os núcleos lógicos do CPU e de núcleos de placas gráficas, utilizando recursos do
CUDA para paralelização do código. O hardware utilizado para a presente investigação é o
apresentado a seguir:
Este código numérico consiste em um algoritmo desenvolvido por Alminhana (2017) para a
análise de dados de ensaios experimentais realizados no túnel de vento de camada limite Prof.
Joaquim Blessmann. O modelo numérico é capaz de calcular os coeficientes de pressão nas
tomadas distribuídas nas fachadas do modelo e ainda determinar os coeficientes
aerodinâmicos locais e globais do mesmo.
Inicialmente, tomou-se o código numérico em Fortran 90 em sua forma serial, isto é, sem
contar com rotinas de paralelização, e fez-se alterações no mesmo com o objetivo de habilitá-
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
53
Além da divisão proposta, para medir o desempenho entre os diferentes tipos de paralelização
de tarefas, inseriu-se medidores de tempo em trechos críticos do código que demandam um
maior número de operações. Abaixo são apresentados os trechos utilizados para medir a
performance dos códigos.
TRECHO 1
Código Serial:
Código OpenMP:
Código CUDA:
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
55
TRECHO 2
Código Serial:
Código OpenMP:
Código CUDA:
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
57
TRECHO 3
Código Serial:
Código OpenMP:
Código CUDA:
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
59
A partir dos trechos expostos no item 3.1, procedeu-se a comparação dos tempos relativos
para a execução dos mesmos. Os resultados obtidos seguem apresentados nas Tabelas 3.1 e
3.2.
Analisando os resultados expostos nas Tabelas 3.1 e 3.2, observa-se que a questão da precisão
das variáveis é determinante para a performance do código em CUDA, uma vez que o tempo
de processamento chega a ser aumentado em 4,96x para o Trecho 1 utilizando kernels em
módulo e 14,1x utilizando KLD, já para os demais o aumento no tempo ficou na margem de
8.59% até 71.1%. Comparando os tempos entre os diferentes tipos de paralelização, tem-se
que para ambos os tipos de precisão, o código em CUDA conseguiu desempenhos bem
superiores para os trechos 1 e 3 do código, já para o segundo trecho, tem-se que a
paralelização via KLD foi a com melhor performance, sendo seguida pela em OpenMP. De
forma resumida, a Tabela 3.3 e a Figura 3.1 ilustram a performance relativa entre os
diferentes tipos de paralelização, tomando como referência os resultados obtidos na versão
serial do código.
Ao final deste estudo, conclui-se que o uso de paralelização utilizando GPUs é capaz de
ocasionar melhoras significativas na performance de códigos numéricos, principalmente em
trechos que demandam uma quantidade significativa de cálculos, tal como no caso do Trecho
1. Além disso, assim como comentado anteriormente, o tipo de precisão das variáveis afeta
diretamente a velocidade de execução dos kernels, onde destaca-se aqui o speedup obtido para
a rotina computacional realizada via KLD para o Trecho 1.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
61
A partir da modificação do algoritmo, que em sua forma original, permitia apenas o uso de
CPUs, seja na resolução de problemas via programa serial ou paralelizada em OpenMP, pode-
se medir o speedup entre uso de GPUs e CPUs. Para isto, utilizou-se a configuração descrita
no início do capítulo 3 e simulou-se numericamente os seguintes problemas: escoamento em
cavidade bidimensional e sobre degrau bidimensional. Ressalta-se que o código de fluidos em
CUDA não será apresentado no presente trabalho devido a extensão do mesmo.
Para testar a performance do uso do CUDA no código numérico proposto por Braun (2007),
simulou-se o problema da cavidade em três situações de simulação, sendo a primeira a versão
serial do programa, a segunda sendo uma versão OpenMP do mesmo, e por fim, tem-se a
versão que conta com KLDs, que possibilitaram a simulação numérica através do uso de
placas gráficas. Ainda se tem que à análise, em termos de performance, foi realizada a partir
dos resultados obtidos em 5 tomadas de dados realizadas para cada tipo de forma de
simulação computacional, tomando-se como referência os obtidos para a versão Serial do
código de fluidos. Um ponto que se destaca na análise realizada é quanto à precisão das
variáveis, que foram declaradas como sendo de precisão dupla. Na Tabela 3.4, que segue
abaixo, são apresentados os resultados obtido na simulação do problema de escoamento em
uma cavidade para os três códigos numérico utilizados.
Tempo (s) Speedup
Tomada
Serial OpenMP CUDA‐KLD Serial OpenMP CUDA‐KLD
1 1939.4 1284.1 2192.1 1.00 1.51 0.88
2 1893.1 1501.0 2143.9 1.00 1.29 0.90
3 1893.1 1297.2 2231.5 1.00 1.50 0.87
4 1891.2 1247.0 2218.8 1.00 1.56 0.87
5 1891.0 1345.4 2144.3 1.00 1.44 0.90
Média 1901.6 1334.9 2186.1 1.00 1.46 0.89
Analisando-se os resultados obtidos para este problema, verifica-se que a versão serial do
código foi capaz de entregar uma melhor performance na capacidade de processamento do
que a obtida utilizando CUDA, com laços escritos em KLD. Em termos de speedups para a
simulação do problema da cavidade, vê-se que o código utilizando CUDA-KLD ficou abaixo
de 1,0, exibindo um valor médio de 0,89. A versão do código em OpenMP foi a que
apresentou melhor performance, visto que os tempos de simulação foram os menores entre
todas as tomadas de dados, resultando ao final, em um speedup médio de 1,46.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
63
Conforme observa-se na Figura 3.3, tem-se que de forma qualitativa os campos de velocidade,
para a componente ao longo do eixo x, da cavidade apresentam resultados semelhantes entre
as versões Serial e CUDA do código de fluidos utilizado na simulação computacional. Já os
resultados obtidos na versão em OpenMP se mostram aquém daqueles obtidos nas demais
versões do código de fluidos. E aqui ressalta-se que todas as simulações foram realizadas
utilizando os mesmos parâmetros de inicialização do programa e no fluido empregado.
Dadas as diferenças observadas nos resultados obtidos entre diferentes versões do código
numérico, comparou-se os mesmos com os apontados por Ghia et al. (1982), uma referência
largamente utilizada para verificar a qualidade dos resultados de códigos computacionais de
simulação de escoamento de fluidos. A comparação realizada é apontada abaixo, na Figura
3.6.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
65
Observando-se os resultados obtidos nas simulações, nota-se que em relação aos dados
apontados por Ghia et al. (1982), as versões Serial e CUDA do código de fluidos apresentam
valores próximos ao do autor tomado como referência, porém verifica-se diferenças nos
resultados, o que pode ser fruto de uma interrupção prematura da simulação numérica que
poderia ter sido realizada por um período maior de tempo. Destaca-se ainda que a versão em
OpenMP conforme os resultados ilustrados na Figura 3.5, ainda não está próxima de atingir a
convergência numérica para os campos de velocidade dentro da cavidade, dado os parâmetros
utilizados na simulação numérica, o que contrasta com os resultados apresentados para as
outras versões do código computacional, que se mostram mais próximos dos apresentados por
Ghia et al. (1982). Neste ponto, destaca-se que apesar de a versão OpenMP ter sido a com
melhor performance computacional, a mesma apresenta resultados aquém das demais, o que
sugere que nesta versão do código pode haver problemas na estruturação das diretrizes de
paralelização.
3.2.2 Degrau
Além dos parâmetros apontados na Figura 3.7, utilizou-se propriedades do fluido que
resultassem em um número de Reynolds (Re) de 400.
Com os parâmetros ajustados para Re=400, simulou-se o problema do degrau para as versões
Serial, OpenMP e CUDA. Os tempos de processamento e speedups obtidos seguem
apresentados na Tabela 3.5. Ressalta-se que para este problema, devido ao tamanho da malha
em Elementos Finitos, fez-se apenas uma tomada de dados.
Tempo (h) Speedup
Tomada
Serial OpenMP CUDA‐KLD Serial OpenMP CUDA‐KLD
1 37.7 27.0 39.4 1.00 1.40 0.96
Conforme apontam os dados apresentados na Tabela 3.5, vê-se que novamente a simulação
em OpenMP fora a que foi concluída em menor tempo, apresentando assim, o maior speedup
entre as diferentes versões do código de fluidos. Após, verifica-se que a versão Serial se
mostrou mais veloz no processamento do problema, finalizando a simulação em 37,7h. Nesse
sentido, conclui-se que a proposta em utilizar kernels com a estrutura de KLD acabou por
gerar um código numérico pouco eficiente em capacidade de processamento para a versão
CUDA, tal como também fora observado no escoamento em uma cavidade. Sendo assim,
nota-se que para que ocorra melhora na performance de processamento do código em CUDA
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
67
é necessário reescrever o código eliminando trocas de dados ao máximo e ainda assim, utilizar
kernels em arranjo de módulos, que possibilitam uma melhor administração da memória da
placa utilizada no processamento computacional das diretrizes contidas no código de fluidos.
Na Figura 3.11, são apresentados os campos de pressão obtidos para cada versão do código de
fluidos utilizado na simulação numérica, que demonstraram resultados similares, indicando
assim que o problema atingiu seu estado permanente.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
69
4 CONCLUSÕES
Na busca realizada por trabalhos na literatura, viu-se que o emprego de GPUs, contando ou
não com recursos de MPI, foi capaz de aumentar substancialmente a velocidade de
processamento de problemas relacionados à Dinâmica de Fluidos e a Engenharia do Vento.
Nos trabalhos explorados, identificou-se speedups que ficaram na faixa de 10x a 100x, em
Nos estudos de caso realizados pelo presente autor do seminário, investigou-se o emprego da
linguagem CUDA para dois algoritmos numéricos, um destinado ao tratamento de dados de
ensaios de pressões em modelos rígidos do túnel de vento Prof. Joaquim Blessmann e o outro
confeccionado por Braun (2007), com ambos demandando um alto nível de processamento
para a realização de dadas tarefas. Na presente investigação, comparou-se o desempenho dos
códigos utilizando CPUs e GPUs/CUDA, em versões paralelas e seriais dos códigos.
Para o primeiro algoritmo investigado, verificou-se altos speedups em relação à versão serial
do código para CPU. Além disso, notou-se que o desempenho de processamento alcançado
pelo CUDA foi superior ao obtido utilizando a versão em OpenMP para 2 dos 3 trechos do
código. Outro ponto que merece destaque nas simulações realizadas foi o desempenho em
precisão simples do CUDA, que atingiu altíssimos speedups, sendo o desempenho superior,
em um dado trecho, 15x em relação ao código OpenMP. Para os resultados advindos das
variáveis declaradas como sendo de dupla precisão, nota-se um decréscimo substancial na
performance do código em CUDA, porém este ainda continua sendo mais veloz na execução
das rotinas programadas.
Para o segundo código investigado, cujo modelo matemático baseia-se na solução das
equações fundamentais da Dinâmica de Fluidos através do emprego de séries de Taylor,
minimização das equações por Bubnov-Galerkin, no contexto dos Elementos Finitos, viu-se
que a construção da versão do código empregando o uso de kernels em KLD constituiu em
uma solução ineficiente, uma vez que o speedup médio alcançado ficou abaixo do
apresentado pela versão Serial. Tal comportamento visto, aponta que a construção do código
de fluidos através de kernels em KLD demanda muitas trocas de informações entre host e
device, o que é um ponto crítico na performance do código utilizando recursos de cálculo de
placas gráficas. Sendo assim, o modelo do código numérico empregado deverá ser alterado de
modo a contar com kernels construídos usando a lógica de módulos, o que será capaz de
reduzir a troca de informações host/device e permitir uma melhora significativa na
performance computacional do código de fluidos contando com os núcleos de processamento
de placas gráficas.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
71
Embora o código de fluidos modificado pelo presente autor, contando com recursos de GPUs,
não tenha apresentado uma performance computacional superior às obtidas nas versões Serial
e OpenMP, vê-se um grande potencial no uso das GPUs, visto a performance obtida no
primeiro código investigado no presente seminário e também nos resultados apresentados por
diversos autores da mesma área acerca do uso do CUDA para a simulação de problemas de
Dinâmica dos Fluidos e de interesse da Engenharia do Vento. Dados os resultados
encontrados para o código de fluidos em versão CUDA, tem-se que a reescrita deste, visando
uma abordagem que reduza a troca de dados entre host e device, possibilitaria um acréscimo
importante na performance computacional, entregando ao final, respostas em um tempo
menor do que a versão em OpenMP.
REFERÊNCIAS
ARMALY, B. F.; DURST, F.; PEREIRA, J. C. F., SCHÖNING, B. 1983. Experimental and
Theoretical Investigation of Backward-Facing Step Flow, Journal of Fluid Mechanics, v.
127, pp. 473-496.
GUIA, U.; GHIA, K. N.; SHIN, C. T. High-Re solutions for incompressible flow using
Navier-Stokes equations and a multigrid method. Journal of Computational Physics, v. 48,
p. 387-411, 1982.
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.
73
PGI Compiler & Tools, 2017. “CUDA Fortran programming guide and reference”. Nvidia
Corporation, 2017, USA.
RUETSCH, G.; FATICA, M. CUDA Fortran for Scientists and Engineers: best practices
for efficient CUDA Fortran programming. 1 ed. Waltham: Elsevier, 2014.
ANEXOS:
__________________________________________________________________________________________
Guilherme Wienandts Alminhana (guilherme.alminhana@gmail.com) Seminário de Doutorado. PPGEC/UFRGS. 2018.