universidade estadual de campinas …lotavio/tgs/2012_partição de domínios para... · resultado...
TRANSCRIPT
i
UNIVERSIDADE ESTADUAL DE CAMPINAS
FACULDADE DE ENGENHARIA MECÂNICA
Relatório Final
Trabalho de Conclusão de Curso
Partição de Domínios para
Processamento em Cluster de GPUs
Autor: Lucas Monteiro Volpe
Orientador: Prof. Dr. Luiz Otávio Saraiva Ferreira
Campinas, novembro de 2012
ii
UNIVERSIDADE ESTADUAL DE CAMPINAS
FACULDADE DE ENGENHARIA MECÂNICA
Relatório Final
Trabalho de Conclusão de Curso
Partição de Domínios para
Processamento em Cluster de GPUs
Autor: Lucas Monteiro Volpe
Orientador: Prof. Dr. Luiz Otávio Saraiva Ferreira
Curso: Engenharia de Controle e Automação
Trabalho de Conclusão de Curso apresentado à Comissão de Graduação da
Faculdade de Engenharia Mecânica, como requisito para a obtenção do título de
Engenheiro de Automação e Controle.
Campinas, 2012
SP – Brasil
iii
Agradecimentos
Agradeço a todos os professores da Unicamp que ministraram as disciplinas
durante o curso por tudo que pude aprender durante o curso.
Agradeço principalmente ao Prof. Luiz Otávio por ter me orientado durante o
trabalho e pela oportunidade de continuar me desenvolvendo nessa área no ano que
vem através do mestrado.
Agradeço minha família pelo apoio dado durante todo o curso de graduação.
Agredeço a todos os meus amigos pelos bons momentos compartilhados e por
me incentivarem durante a graduação.
iv
Índice
Agradecimentos ................................................................................... iii
Índice ................................................................................................... iv
Resumo ................................................................................................. 1
Lista de Figuras ..................................................................................... 2
Lista de Tabelas .................................................................................... 4
Nomenclatura ........................................................................................ 5
Abreviações e Siglas ............................................................................. 6
Capítulo 1 Introdução ............................................................................................. 7
Capítulo 2 Computer Unified Device Architeture (CUDA) ....................................... 8
2.1 API CUDA ........................................................................................... 10
2.2 Hierarquia de memória ........................................................................ 12
2.3 Utilização de POSIX Threads com CUDA ........................................... 14
2.4 Programação CUDA em Múltiplas GPUs............................................. 14
2.5 Programação CUDA em clusters ......................................................... 14
Capítulo 3 O Método Lattice Boltzmann ............................................................... 16
3.1 A equação de Boltzmann .................................................................... 16
3.2 O Modelo Lattice Boltzmann................................................................ 17
3.3 O Método Lattice Boltzmann ............................................................... 19
3.4 Condições de Fronteira ....................................................................... 20
3.5 Viscosidade ......................................................................................... 21
Capítulo 4 Programa latibol .................................................................................. 23
4.1 Funcionamento do programa latibol .................................................... 29
4.2 Inicialização ......................................................................................... 30
4.3 Arquivos de saída ................................................................................ 32
4.4 Propagação ......................................................................................... 32
4.5 Colisão ................................................................................................ 34
4.6 Finalização .......................................................................................... 35
Capítulo 5 Método de Ensaio ............................................................................... 36
Capítulo 6 Resultados .......................................................................................... 38
Capítulo 7 Conclusão ........................................................................................... 51
Referência Bibliográfica ....................................................................... 52
Apêndice ............................................................................................. 53
v
latibol.h: ............................................................................................... 53
latibol.cu: ............................................................................................. 55
initialize.h: ........................................................................................... 62
initialize.cu: .......................................................................................... 62
streaming.h: ........................................................................................ 73
streaming.cu: ....................................................................................... 73
get_border.h: ....................................................................................... 77
get_border.cu: ..................................................................................... 77
copy_border.h: .................................................................................... 80
copy_border.cu: ................................................................................... 80
apply_border.h: ................................................................................... 83
apply_border.cu: .................................................................................. 83
collision.h: ........................................................................................... 86
collision.cu: .......................................................................................... 86
save.h:................................................................................................. 92
save.cu: ............................................................................................... 92
cudaFree_data.h: ................................................................................ 94
cudaFree_data.cu: .............................................................................. 94
1
Resumo
VOLPE, Lucas Monteiro, Partição de Domínios para Processamento em Cluster de
GPUs, Faculdade de Engenharia Mecânica, Universidade Estadual de Campinas,
Trabalho de Conclusão de Curso (2012), 101 pp.
Este trabalho de graduação tem como objetivo distribuir o processamento em
um cluster de GPUs de uma simulação de fluídos através do método lattice
Boltzmann, para casos em duas e três dimensões. O software foi implementado em
linguagem CUDA C, para o processamento em GPUs, e em MPI, para realização da
troca de informações entre os nós do cluster. Foram realizados testes em um cluster
de quatro nós com quatro GPUs em cada para diferentes tamanhos de domínio e
diferentes configurações, considerando diferentes quantidades de GPUs ou o uso de
uma CPU. Os resultados demonstram um grande aumento de desempenho com o
uso de GPUs em relação à programação clássica. Observa-se também um aumento
de eficiência do processamento em múltiplas GPUs conforme se aumenta o
tamanho do domínio, justificando o uso de mais de uma GPU a partir de certos
tamanhos de domínio.
Palavras Chave: Computação paralela, cluster, GPU, CUDA, MPI.
2
Lista de Figuras
Figura 2.1. Capacidade de processamento de GPUs Nvidia e Processadores Intel .. 8
Figura 2.2. Organização de transístores em GPUs e CPUs ....................................... 8
Figura 2.3. GPU Fermi. ............................................................................................ 10
Figura 2.4. Grid 2x3 com blocos 4x3. ....................................................................... 11
Figura 2.5. Exemplo de execução de um programa CUDA ...................................... 11
Figura 3.1. Modelo lattice Boltzmann D2Q9 ............................................................. 18
Figura 3.2. Modelo lattice Boltzmann D3Q19 ........................................................... 18
Figura 3.3. Topologia de domínios para uma direção (esq.) e duas direções (dir.) ... 20
Figura 3.4. Movimento das densidades f a para a condição de fronteira bounce back
................................................................................................................................. 21
Figura 4.1. Imagem ‘input.bmp’ de tamanho 128x64 simulando condição no-slip .... 25
Figura 4.2. Exibição do terminal após execução do exemplo ................................... 26
Figura 4.3. Arquivos de saídas ‘r0000.pvti’ (acima) e ‘r0001.pvti’ (abaixo) ............... 26
Figura 4.4. Perfil de velocidades simulado (azul) e aproximação quadrática (preto) 27
Figura 4.5. Arquivo ‘input.bmp’ utilizado no exemplo 3D .......................................... 28
Figura 4.6. Exibição no terminal após execução do exemplo 3D .............................. 28
Figura 4.7. Resultado do arquivo de saída ‘r0001.pvti’ cortado ao meio em x .......... 28
Figura 4.8. Perfil de velocidades simulado (azul) e aproximação quadrática (preto)
para o exemplo 3D ................................................................................................... 29
Figura 4.9. Distribuição de um domínio entre 6 devices ........................................... 30
Figura 4.10. Distribuição das fronteiras durante a propagação em dois nós com dois
devices cada ............................................................................................................ 34
Figura 6.1. Velocidade de execução dos testes em relação à área do domínio para
diferentes sistemas (D2Q9) ...................................................................................... 39
Figura 6.2. Velocidade de execução dos testes em relação à área do domínio para
diferentes sistemas (D3Q19) .................................................................................... 39
Figura 6.3. Ganho de velocidade de execução em relação a CPU (D2Q9) .............. 41
Figura 6.4. Ganho de velocidade de execução em relação a CPU (D3Q19) ............ 41
Figura 6.5. Ganho de velocidade em relação ao mínimo necessário de GPUs
(D2Q9) ..................................................................................................................... 42
Figura 6.6. Ganho de velocidade em relação ao mínimo necessário de GPUs
(D3Q19) ................................................................................................................... 42
3
Figura 6.7.Eficiência do processamento em relação ao mínimo necessário de GPUs
(D2Q9) ..................................................................................................................... 43
Figura 6.8. Eficiência do processamento em relação ao mínimo necessário de GPUs
(D3Q19) ................................................................................................................... 43
Figura 6.9. Ganho de velocidade em relação ao mínimo necessário de nós (D2Q9)44
Figura 6.10. Ganho de velocidade em relação ao mínimo necessário de nós (D3Q19)
................................................................................................................................. 45
Figura 6.11. Eficiência do processamento em relação ao mínimo necessário de nós
(D2Q9) ..................................................................................................................... 45
Figura 6.12. Eficiência do processamento em relação ao mínimo necessário de nós
(D3Q19) ................................................................................................................... 46
Figura 6.13. Velocidade de execução dos testes em relação à área do domínio
comparando 1 nó com 4 devices e 2 nós com 2 devices cada (D2Q9) .................... 47
Figura 6.14. Velocidade de execução dos testes em relação à área do domínio
comparando 1 nó com 4 devices e 2 nós com 2 devices cada (D3Q19) .................. 47
Figura 6.15. Eficiência do processamento em relação a 1 nó com 4 devices (D2Q9)
................................................................................................................................. 48
Figura 6.16 Eficiência do processamento em relação a 1 nó com 4 devices (D3Q19)
................................................................................................................................. 48
Figura 6.17. Tempo para salvar arquivo de saída em relação a área do domínio
(D2Q9) ..................................................................................................................... 49
Figura 6.18 Tempo para salvar arquivo de saída em relação a área do domínio
(D3Q19) ................................................................................................................... 49
4
Lista de Tabelas
Tabela 4.1. Estrutura de dados DataStruct ......................................................... 31
Tabela 4.2. Estrutura de dados SaveStruct .......................................................... 31
Tabela 5.1. Tamanhos dos domínios utilizados nos testes 2D ................................. 36
Tabela 5.2. Tamanhos dos domínios utilizados nos testes 3D ................................. 37
Tabela 6.1. Velocidade de execução dos testes (D2Q9) .......................................... 38
Tabela 6.2. Velocidade de execução dos testes (D3Q19) ........................................ 38
5
Nomenclatura
Letras Latinas
f Função de distribuição de probabilidades -
x Vetor de posição -
p Momento linear [mu.lu/ ts ]
n Número de partículas -
t Tempo [t s ]
e Velocidade microscópica [lu/ ts ]
q Quantidade de direções -
u Velocidade macroscópica [lu/ ts ]
w Peso da função de distribuição de equilíbrio -
c Velocidade de propagação [lu/ ts ]
Letras Gregas
Γ( + )
Quantidade de partículas que não vieram da condição
esperada
-
Γ( - )
Quantidade partículas que não foram para a condição
esperada
-
ρ Densidade [mu/lu3 ]
ν Viscosidade [lu2/ ts ]
τ Fator de relaxamento -
Subscritos
a Direção -
6
Abreviações e Siglas
CUDA Computer Unified Device Architeture
MPI Message Passing Interface
GPU Unidade de Processamento Gráfico
CPU Unidade Central de Processamento
UVA Endereço Virtual Unificado
LBM Método lattice Boltzmann
BGK Bhatagner, Gross e Krook
NFS Network File System
7
Capítulo 1
Introdução
Há cinco anos, foi apresentada pela Nvidia, empresa líder no segmento de
placas de vídeo de alto desempenho, a tecnologia CUDA, possibilitando a utilização
da placa de vídeo para execução de programas. Essa tecnologia surgiu em uma
época em que as placas de vídeo já apresentavam um poder de computação maior
comparado com o dos processadores. Porém esse poder de processamento só
pode ser atingido em aplicações altamente paralelas, nas quais um problema é
subdividido de forma que cada parte seja executada ao mesmo tempo, sendo
geralmente utilizado para acelerar simulações computacionais.
Um dos tipos de simulação que pode ser realizada de forma paralela é a
simulação de fluídos utilizando o método lattice Boltzmann, que realiza a
discretização da equação de Boltzmann e, através de funções probabilísticas, simula
o comportamento de fluídos em interações com sólidos ou outros fluídos.
O presente trabalho tem como objetivo implementar e distribuir o
processamento de uma simulação de fluídos utilizando o método lattice Boltzmann
para que seja executada em um cluster com múltiplas placas de vídeo. Como o foco
é a distribuição do processamento, é tratado um caso simples para a simulação,
sendo a interação de um fluído em uma determinada velocidade inicial com sólidos
utilizando domínios de duas ou três dimensões. O programa é implementado
utilizando a linguagem CUDA C com auxílio de MPI para realização da troca de
informações entre os nós do cluster.
Por final são comparadas as velocidades de processamento do programa
executado tanto em processadores, como em placas de vídeo, para diferentes
configurações, com a finalidade de mostrar todo o potencial da distribuição do
processamento em um cluster.
8
Capítulo 2
Computer Unified Device Architeture (CUDA)
Nos últimos anos tem-se observado um grande aumento na capacidade
computacional das unidades de processamento gráficas (GPUs). Através da figura 1
é possível observar esse fato tanto para o número de operações por segundo como
para largura de banda das GPUs Nvidia em relação aos processadores da Intel.
Figura 2.1. Capacidade de processamento de
GPUs Nvidia e Processadores Intel. [1]
Esse poder de processamento maior das GPUs se deve ao fato de que elas
tratam os dados de forma altamente paralela, já que para as aplicações gráficas
normalmente realizam conjuntos de mesmas operações ao mesmo tempo. Na figura
2 é possível observar a diferença entre a forma como uma CPU é organizada e uma
GPU, percebendo-se que em uma GPU existem mais transistores para o
processamento de dados e menos para o controle de fluxo e memória cache. [1]
Figura 2.2. Organização de transístores em GPUs e CPUs. [1]
9
Sendo assim, em 2007 a Nvidia lançou a Arquitetura CUDA (Computer Unified
Device Architeture), possibilitando a programação das GPUs para processamento
paralelo de dados, e não somente para aceleração gráfica. Para sua programação é
utilizada a linguagem CUDA C, que é basicamente uma extensão da linguagem C e
possibilita a programação paralela.
Desde seu lançamento, a arquitetura CUDA já passou por algumas
atualizações, a medida que foram surgindo novas arquiteturas para as GPUs, com
novas funcionalidades. Por isso é bom sempre estar atento nas capacidades da
GPU que será utilizada para execução do programa. Essas capacidades são
definidas pela versão de Compute Capability da GPU. A arquitetura G80 da Nvidia
lançou a versão 1.0 contendo 8 núcleos CUDA por multiprocessador, a qual foi
sofrendo algumas melhorias até chegar na versão 1.4.
A versão 2.0 foi lançada em 2010 com a arquitetura Fermi, aumentando para
32 núcleos CUDA por multiprocessador e adicionando o cache L1 e L2, tendo uma
atualização para versão 2.1 com 48 núcleos por multiprocessador. Com a versão 2.0
também foram introduzidas funções de acesso direto a memória entre GPUs, não
necessitando mais a criação de buffers na memória RAM para transferência de
dados entre GPUs. No início de 2012, foi introduzida a arquitetura Kepler na versão
3.0, agora com 192 núcleos CUDA por multiprocessador, e com grandes novidades,
como acesso direto na memória entre GPUs que estão em diferentes nós de um
cluster e o paralelismo dinâmico, que permite que funções executadas na GPU
sejam chamadas dentro de outras funções que já estão sendo executado na GPU,
possibilitando chamadas recursivas e minimizando o uso da CPU durante a
execução do programa. [3]
Para este trabalho são utilizadas GPUs Geforce GTX 580, as quais possuem
GPUs Fermi com capacidade de computação versão 2.0. Cada GPU Fermi da
Geforce GTX 580 possui 512 núcleos CUDA, organizados em 16
multiprocessadores de 32 núcleos. Cada núcleo possui uma unidade lógica
aritmética para números inteiros e uma para ponto flutuante. A estrutura da GPU
pode ser vista na figura abaixo. Na figura é possível ver os 16 multiprocessadores
compartilhando a mesma memória L2, sendo que cada multiprocessador é uma
10
fileira vertical contendo uma parte laranja (scheduler e dispatch), uma parte verde
(unidades de execução) e uma azul claro (registradores e cache L1). [4]
Figura 2.3. GPU Fermi. [4]
2.1 API CUDA
A execução de um programa em CUDA funciona da seguinte forma, a CPU
(host) chama funções que são executadas na GPU (device), as quais são
denominadas de kernel. Um thread é um pedaço de um kernel ao qual será
executado em paralelo com outros threads do mesmo kernel. Os threads são
organizados em blocos, que juntos formam um grid. Sendo assim, um grid é um
conjunto de blocos de threads que executam o mesmo kernel, leem e escrevem na
memória global e são sincronizados conforme solicitado pelo kernel. A figura 4
mostra um grid de dimensão 2x3 com blocos de tamanho 3x4. [4]
A quantidade de threads por bloco é limitada de acordo com a GPU, sendo que
nas mais modernas, pode haver até 1024 threads por bloco. Cada bloco é
executado por um multiprocessador, então é ideal que a quantidade de blocos de
um kernel seja no mínimo a quantidade de multiprocessadores da GPU. Durante a
execução de um bloco, seus threads são agrupados em estruturas denominadas
warps, sendo que cada warp possui 32 threads e são executados de forma paralela
dentro do multiprocessador.
11
Na figura 5 é apresentado um modelo de um aplicativo CUDA, onde a
sequência de códigos em série é executada pelo host e os códigos em paralelo pelo
device.
Figura 2.4. Grid 2x3 com blocos 4x3. [1]
Figura 2.5. Exemplo de execução de um programa CUDA. [1]
12
2.2 Hierarquia de memória
Um dos aspectos mais importantes da programação em CUDA é conseguir
minimizar os custos das transferências de dados dentro do device e utilizar ao
mínimo a transferência de dados entre o host e o device, já que a largura de banda é
de 8 GB/s no barramento PCIe X16, que é bem inferior aos 192,4 GB/s de uma
GeForce GTX 580 [3]. Para isso é necessário entender como funcionam os diversos
tipos de memórias dentro de uma GPU e quais as diferenças de acesso e
velocidade de cada uma.
Memória Global: Memória com maior capacidade da GPU e também a com
maior latência de acesso, podendo ser acessada para leitura ou escrita por qualquer
thread ou pelo host. Na arquitetura Fermi, a memória global passou a possuir
caches L1 (48KB por multiprocessador) e L2 (768KB compartilhados pelos
multiprocessadores), melhorando o tempo de acesso para dados que já estão em
cache.
Memória Compartilhada (shared memory): Memória presente em cada
multiprocessador e possui acesso para leitura e escrita apenas para os threads que
estão no bloco que está sendo executado pelo multiprocessador. É a memória com
acesso mais rápido, pois está presente dentro do chip da GPU, e por padrão possui
16KB para cada multiprocessador, podendo ser configurada para até 48KB para
GPUs Fermi. [3]
Memória Constante (constant cached memory): Memória somente-leitura de
64KB que possui 8KB de cache por multiprocessador. As vantagens de seu uso são:
capacidade de executar meio warp com apenas um acesso a memória, caso os
dados a serem lidos sejam os mesmos para todos os threads; cache ser
relativamente grande em relação ao tamanho total, possibilitando que boa parte dos
acessos sejam feitos diretamente do cache. [2] [3]
Memória de Textura (texture cached memory): Memória somente-leitura com
cache de até 8KB que tem a propriedade de ter acessos mais rápidos quando um
thread acessa dados que estão na vizinhança 2D do thread executado
13
anteriormente. Sua utilização é recomendada quando um programa exibe um padrão
de acesso espacial a memória. [2]
Registradores: Memória ultra-rápida que armazena os dados de um thread
necessários para execução de suas instruções. As GPUs Fermi possuem 63
registradores por thread. [3]
Memória Local: Memória alocada automaticamente por um thread quando a
quantidade de registradores disponíveis é menor que o necessário. [3]
Para aplicações CUDA, também existem diversas formas de mapear a
memória que será utilizada no host. Essas formas são apresentadas abaixo:
Paginada: É a forma padrão utilizada na linguagem C. Aloca memória
paginável pelo sistema operacional, ou seja, o seu endereço físico pode ser alterado
a qualquer momento dependendo da necessidade do sistema operacional.
Page-Locked Host Memory: Também conhecida como memória pinada, foi
introduzida na linguagem CUDA, permitindo a criação de memória não paginável
pelo sistema operacional. Como seu endereço físico não é alterado, sua utilização
permite que a GPU tenha acesso direto a memória (DMA) do host, não necessitando
a utilização da CPU, e assim proporcionando um acesso mais rápido e seguro.
Porém, seu uso deve ser feito com cautela, pois ela limita os recursos de memória
do sistema para outras aplicações. [2]
Zero-Copy Host Memory: Memória com as mesmas características da memória
pinada, porém permite que seja acessada e modificada diretamente pelo kernel, não
necessitando ser copiada para a memória do device antes do processamento dos
dados. Sua utilização é recomendada quando os dados só serão acessados uma
vez pelo kernel, economizando o tempo de cópia da memória, ou quando o sistema
possui unidades CUDA na própria placa mãe e já utiliza uma parte da memória host
como sendo do device. [2]
Portable Pinned Memory: Memória igual à pinada, porém seu uso se estende a
mais de um device, sendo utilizada em programas que usam mais de uma GPU.
14
Caso todos os devices do sistema utilizem o recurso de espaço de endereço virtual
unificado (UVA) introduzido a partir da versão 2.0, seu uso tornou-se desnecessário,
já que o recurso UVA permite o acesso direto da memória de um device a partir de
outro device, sem precisar de um buffer no host para realizar a troca de dados. [3]
2.3 Utilização de POSIX Threads com CUDA
Também é possível dividir o processamento de um programa CUDA em POSIX
threads, também conhecido como Pthreads. A utilização de Pthreads permite que a
execução de uma parte do programa seja feita em um núcleo do processador
enquanto outro núcleo executa outra parte em paralelo. Em um programa CUDA as
Pthreads podem ser utilizadas para executar algo que necessite da CPU enquanto
ocorre o processamento na GPU, por exemplo, salvar um arquivo de saída enquanto
a GPU continua a processar os dados do próximo arquivo de saída. [11]
2.4 Programação CUDA em Múltiplas GPUs
Atualmente é possível ter até oito GPUs conectadas em uma mesma placa-
mãe, sendo possível distribuir a execução de um programa CUDA utilizando mais de
um device em cada nó de um cluster.
Para implementar programas em múltiplas GPUs, primeiro é necessário definir
uma estrutura com todas as variáveis que são alocadas na memória da GPU, sendo
criado um vetor dessas estruturas com o tamanho da quantidade de GPUs
presentes no sistema.
Antes da execução do kernel, deve-se definir qual GPU irá executá-lo através
da função cudaSetDevice() e depois chamar o kernel passando como parâmetro
a estrutura que está alocada na GPU que foi definida.
2.5 Programação CUDA em clusters
Também é possível distribuir o processamento em múltiplas estações com
múltiplas GPUs, através da programação em Message Passing Interface (MPI). O
MPI é uma extensão da linguagem C assim como CUDA, que possui um conjunto de
15
funções utilizadas para a comunicação entre computadores [5]. Felizmente é
possível realizar chamadas de aplicações CUDA dentro de um programa em MPI,
facilitando muito a adaptação de um programa CUDA em MPI.
As funções de MPI basicamente realizam o envio e o recebimento de
mensagens de dados entre processos. Quando um programa em MPI é executado,
é definido pelo usuário o número de processos que serão iniciados e como eles
serão distribuídos, podendo ser entre os diversos núcleos de um processador ou
entre os nós de um cluster. Quando o programa é iniciado, é atribuído um número
de posição para cada processo, sendo este número que define o que será
executado por cada processo, possibilitando distribuir o processamento de acordo
com as necessidades do programa.
16
Capítulo 3
O Método Lattice Boltzmann
Ludwig Boltzmann (1844-1906) foi um físico austríaco que desenvolveu
métodos estatísticos para descrever o comportamento de partículas. A ideia
fundamental de seu trabalho é que as interações de partículas de um gás podem ser
descritas baseada na mecânica clássica, e, pelo fato de existir muitas partículas, é
necessário um tratamento estatístico adequado das interações. [6]
3.1 A equação de Boltzmann
Um sistema dinâmico pode ser descrito como diversas partículas que possuem
um vetor de posição (x) e um momento linear (p) para cada partícula em
determinado instante de tempo. Essas informações fornecem o estado dinâmico
atual do sistema que, através das leis da mecânica clássica, torna-se possível
prever qualquer estado futuro. [6]
Sendo assim, um sistema pode ser descrito como uma função de distribuição
f( n )
(xn,pn, t ) , com n igual ao número de partículas. Quando se quer analisar
propriedades que não dependem da posição, pode-se definir uma função de
distribuição f ( 1 )(x,p , t ) para descrever o sistema. Essa função de distribuição indica
a probabilidade de encontrar uma partícula em uma dada posição e momento linear.
[6]
A determinação de f( 1 ) num tempo t+dt e dada pela equação que define o
processo de propagação:
. (1)
Porém, é preciso levar em conta também que ocorrem colisões. Considerando
Γ ( -)dxdpdt como o número de partículas que não foram para a condição esperada
e Γ(+)dxdpdt o de partículas que não vieram da condição esperada, modifica-se a
equação (1) para:
17
. (2)
Realizando uma expansão de primeira ordem da série de Taylor, chega-se na
equação de Boltzmann:
. (3)
Como é muito difícil de encontrar uma solução para equação de Boltzmann, o
Método de Lattice Boltzmann utiliza a discretização da equação (2) para se
encontrar uma solução aproximada [6]. O método lattice Boltzmann utiliza uma
versão aproximada da equação original, discretizada no espaço, tempo e momento e
projetada em um reticulado.
3.2 O Modelo Lattice Boltzmann
O modelo lattice Boltzmann considera a posição de cada partícula como um nó
do lattice e sua velocidade distribuída nas direções dos nós vizinhos, de forma que
cada direção tem um peso apropriado [6]. Os modelos mais utilizados são o D2Q9
para duas dimensões e nove direções e o D3Q19 para três dimensões e dezenove
direções. O modelo de um nó D2Q9 é apresentado na figura 3.1 e o D3Q19 na
figura 3.2, sendo e a as velocidades microscópicas em cada direção a . A distância
entre nós é definida por unidades lattice (lu ) e o tempo em passos de tempo (t s).
18
Figura 3.1. Modelo lattice Boltzmann D2Q9. [6]
Figura 3.2. Modelo lattice Boltzmann D3Q19. [10]
Considerando f a como a densidade do fluido em cada direção a , e q sendo a
quantidade de direções do método, tem-se que a densidade macroscópica é:
∑ (4)
19
A velocidade macroscópica u também pode ser calculada como a somatória
das médias das velocidades microscópicas, sendo:
∑
(5)
3.3 O Método Lattice Boltzmann
Como comentado anteriormente, o método lattice Boltzmann (LBM) baseia-se
na discretização da equação (2), para isso primeiro é necessário uma aproximação
para o termo da colisão. Em 1954 Bhatagner, Gross e Krook (BGK) definiram o
seguinte modelo simplificado para o termo da colisão:
(6)
Sendo τ denominado fator de relaxamento e fe q a função de distribuição de
Maxwell-Boltzmann, a qual define o equilíbrio local. [7]
Substituindo o termo da colisão e discretizando a equação (2) chega-se na
equação que define o método:
(7)
Onde o termo direito do lado direito da igualdade representa a colisão e o
restante representa a propagação. No método, primeiramente é feito o cálculo da
propagação e depois acrescentado a colisão, que dependerá das condições de
fronteira da partícula.
Durante a propagação o que ocorre é o transporte da densidade f a para o nó
lattice mais próximo de acordo com sua direção.
Já para a colisão, primeiro é necessário calcular a função de distribuição de
Maxwell-Boltzmann, que é definida da seguinte forma:
20
[
] (8)
Para o modelo D2Q9, os pesos w a são 16/36 para a=0 , 4/36 para a=1. . .4 , e
1/36 para a=5. . .8 , e c é a velocidade de propagação em lu/ ts . [8] Já para o
modelo D3Q19, esse pesos ficam sendo 12/36 para a=0 , 2/36 para a=1. . .6 e
1/36 para a=7. . .18 . [10]
A partir da equação (8) é possível calcular o termo da colisão para interações
entre as partículas do fluído. Mas caso ocorra colisão entre a partícula de um fluído
e um sólido, se torna necessária a aplicação das condições de fronteira antes de
realizar a colisão, por isso a necessidade de dividir o calculo da equação (7) em
duas partes.
3.4 Condições de Fronteira
Um nó lattice pode ter diversas formas de condições de fronteira, sendo as
mais básicas a condição de fronteira periódica e a bounce back.
A condição de fronteira periódica é o tipo mais simples que pode ser aplicado.
Aplicando uma condição deste tipo, basicamente faz-se a ligação de extremidades
do domínio de acordo com as direções desejadas. A figura 3.3 mostra as topologias
dos domínios quando aplicadas condições de fronteira para uma direção e para
duas direções em um modelo de duas dimensões.
Figura 3.3. Topologia de domínios para uma direção (esq.) e duas direções (dir.). [6]
21
A condição de fronteira bounce back trata a interação das partículas do fluido
com sólidos. Quando uma partícula tem um nó laticce sólido como vizinho, este nó
armazena as densidades vindas da partícula durante a propagação e as devolvem
na direção oposta durante a propagação do próximo passo de tempo. A figura 8
ilustra esse procedimento.
Figura 3.4. Movimento das densidades f a para a condição de
fronteira bounce back. [6]
3.5 Viscosidade
A viscosidade cinemática ν , dada em lu2/ t s , se relaciona com o fator de
relaxamento τ de acordo com a seguinte equação:
(
) (9)
Pela análise da equação é possível perceber que τ deve ser maior ou igual 0,5
para garantir a viscosidade positiva. Também se percebe que o limite para τ → 0,5
22
representa um fluxo invíscido e que o limite para τ → ∞ a condição de fluxo de
Stokes. Para o caso de fluxo invíscido pode se ter problemas de estabilidade para se
utilizar o LBM, pois se a resolução do lattice não for suficientemente pequena, as
variações de velocidade podem se tornar muito grandes e o modelo pode não
conseguir dissipar toda a energia devido à baixa viscosidade. [9]
23
Capítulo 4
Programa latibol
O programa latibol é um simulador do método lattice Boltzmann desenvolvido
em CUDA C e MPI que permite a execução paralela em múltiplas GPUs e em
diversos nós de um cluster.
O simulador permite realizar simulações para o modelo lattice Boltzmann D2Q9
ou D3Q19 de um tipo de fluido com suporte a sólidos com condição de fronteira
bounce-back e em um domínio periódico.
O programa deve ser compilado e executado em sistema operacional Linux.
Para compilar é necessário ter instalado o Cuda Toolkit na versão 4.0 ou superior e o
OpenMPI, bastando executar o comando make dentro da pasta latibol.
Após a compilação, para executar o programa deve-se entrar na pasta
‘\bin\linux\release’, e chamar o programa através do comando mpiexec no
terminal conforme descrito abaixo.
mpiexec -n X --hostfile <arquivo de hosts> -x LD_LIBRARY_PATH
latibol
Este comando executa o programa latibol em X processos, sendo os processos
distribuídos de acordo com o <arquivo de hosts>, que deve ser preenchido com o
nome das máquinas que correspondem a cada nó, podendo ser indicados a
quantidade de processos que serão iniciados em cada máquina da seguinte forma:
node00
node01 slots=2
node02 slots=4
Neste caso o computador node00 executará o primeiro processo, o node01
executará os 2 seguintes e o node02 os 4 próximos, sendo que caso seja solicitado
24
a execução de mais que 7 processos, o oitavo será executado pelo node00,
seguindo a mesma ordem anterior até o último processo.
Um fator importante para que a execução do programa em mais de um nó
ocorra com sucesso, é a necessidade de que a pasta em que o programa esteja
sendo executado esteja compartilhada entre os nós, podendo utilizar, por exemplo,
um servidor NFS (Network File System).
Durante a execução, as condições iniciais são lidas do arquivo texto
‘parameters.ini’ e do arquivo de imagem ‘input.bmp’ em formato bitmap
monocromático.
O arquivo ‘parameters.ini’ deve conter os seguintes dados na seguinte ordem:
- Quantidade de devices por nó (0 indica a execução pela CPU); - Profundidade do domínio (> 1 define a execução do método D3Q19) [lu ]; - Velocidade inicial do fluido em x [lu/ts ]; - Velocidade inicial do fluido em y [lu/ ts ]; - Velocidade inicial do fluido em z [lu/ ts ]; - Viscosidade do fluido [lu
2/ t s ];
- Densidade inicial do fluido [mu/lu3 ];
- Número total de passos de tempo [t s ]; - Número de passos entre cada arquivo de saída salvo [t s ];
Abaixo é apresentado um exemplo em que é executado o modelo D2Q9 em
uma GPU com velocidade em x de 0,05 lu/ ts , velocidade nula em y , viscosidade de
0,01 lu2/ t s , densidade de 1 mu/lu
3 e sendo executados 20.000 passos de tempo,
com a saída de dados salva apenas no início e no final da execução.
‘parameters.ini’: 1
1
0.05
0.00
0.00
0.01
1.00
20000
20000
25
O arquivo de imagem serve para definir o tamanho do domínio em x e y e
definir quais nós são sólidos. É importante olhar o tamanho do domínio que o
programa detectou em uma primeira execução teste, para definir o tamanho da
imagem em bitmap, pois devido a distribuição do domínio ser feita de forma igual
entre as GPUs, pode ocorrer a perda de partes das bordas esquerda e inferior na
execução do programa. Na imagem, os pixels em preto definem o nós sólidos e os
brancos definem o fluido. Um exemplo de imagem para um domínio de 128x64 com
sólidos nas bordas superiores e inferiores, simulando a condição no-slip, é
apresentado na figura abaixo.
Figura 4.1. Imagem ‘input.bmp’ de tamanho 128x64 simulando condição no-slip
Os arquivos de saída são do tipo VTK Parallel Image Data nomeados como
‘rxxxx.pvti’, sendo xxxx o número da saída em ordem crescente. Esses arquivos
podem ser lidos através do programa Paraview.
Durante a execução, primeiramente é mostrado no terminal quantos devices o
sistema possui e os valores dos parâmetros lidos das entradas na seguinte ordem:
largura, altura, profundidade, velocidade em x , velocidade em y , velocidade em z ,
viscosidade, densidade, o valor de τ devido à viscosidade, número de passos e
passos entre cada saída gerada. Depois, conforme o programa salva as saídas, é
mostrado o nome do arquivo de saída, seguido do tempo para salvar a saida, do
tempo de execução atual e da velocidade com que foi calculado cada passo de
tempo em t s /s .
Como exemplo, será executado o programa com o arquivo ‘parameters.ini’ e a
imagem ‘input.bmp’ dados como exemplo acima em um sistema com um device. Na
figura 4.2 é mostrada a saída que o terminal apresentou após a execução do
exemplos e na figura 4.3 os dois arquivos de resultados abertos no Paraview.
26
Figura 4.2. Exibição do terminal após execução do exemplo
Figura 4.3. Arquivos de saídas ‘r0000.pvti’ (acima) e ‘r0001.pvti’ (abaixo)
Como o exemplo apresentado simula o fluxo de um fluído entre duas paredes,
é possível validar a simulação comparando com um caso real em que a curva de
velocidades apresenta um comportamento parabólico. Observando a figura abaixo,
percebe-se que o perfil de velocidades obtido na simulação é muito próximo da
aproximação para uma parábola, confirmando o sucesso da simulação.
27
Figura 4.4. Perfil de velocidades simulado (azul) e aproximação quadrática (preto)
Para realizar uma simulação em 3 dimensões, basta definir um valor maior que
1 de profundidade no arquivo ‘paramters.ini’, conforme exemplo abaixo:
‘parameters.ini’: 1
128
0.00
0.00
0.05
0.01
1.00
10000
10000
Executando o programa com o arquivo ‘parameters.ini’ apresentado acima e a
imagem ‘input.bmp’ conforme a figura 4.5, obtém-se a tela da figura 4.6 e o resultado
do arquivo ‘r0001.pvti’ conforme figura 4.7.
28
Figura 4.5. Arquivo ‘input.bmp’ utilizado no exemplo 3D
Figura 4.6. Exibição no terminal após execução do exemplo 3D
Figura 4.7. Resultado do arquivo de saída ‘r0001.pvti’ cortado ao meio em x
29
O comportamento de curva de velocidade resultando no caso D3Q19 também
se aproxima muito do resultado esperado, conforme figura 4.8.
Figura 4.8. Perfil de velocidades simulado (azul) e aproximação
quadrática (preto) para o exemplo 3D
4.1 Funcionamento do programa latibol
O programa latibol é implementado em linguagem CUDA C, utilizando recursos
de programação MPI e POSIX Threads. O programa é implementado de forma a
utilizar os recursos de endereço de memória compartilhado, necessitando uma
GPUs com capacidade de computação na versão 2.0 ou superior.
A estrutura básica do programa consiste de:
Inicialização;
Repete para a quantidade de passos definido:
o Calculo da fase de propagação;
o Troca de dados entre GPUs e entre nós do cluster;
o Aplicação das condições de fronteira;
o Cálculo da fase de colisão;
o Salva resultado no formato VTK conforme definido;
Finalização;
30
As etapas da estrutura do programa são abordadas nas próximas subseções.
4.2 Inicialização
Na etapa de inicialização do programa o que ocorre são a criação das variáveis
que serão utilizadas pela CPU, a leitura dos arquivos de entrada ‘parameters.ini’ e
‘input.bmp’, a alocação das memórias utilizadas pela GPU e CPU e a transferência
dos dados da condição inicial para a memória de uma ou mais GPUs.
O arquivo ‘input.bmp’ é lido de forma a definir os sólidos nas direções x e y .
Quando é definida uma profundidade z para o sistema, os sólidos são repetidos
igualmente para cada valor de z . A divisão do arquivo de sólidos entre os devices ou
as CPUs é feita de forma a dividir igualmente o domínio realizando cortes em x ,
conforme mostrado na figura abaixo.
Caso o programa seja executado em GPUs, o tamanho dos blocos CUDA que
é definido é de 32x4x1. Sendo assim os domínios são dividos de forma a serem
múltiplos de 32 em x e 4 em y , podendo ocorrer perda de solídos definidos no
arquivo ‘input.bmp’.
Para realizar a distribuição do programa entre diversas GPUs, é definida uma
estrutura de dados DataStruct contendo todas as variáveis que serão necessárias
pela GPU. Essas variáveis são apresentadas na tabela abaixo, sendo que as
variáveis que são definidas como ponteiros, são alocadas em forma de matriz com o
tamanho da quantidade de nós lattice do domínio que será executado na GPU.
1 2 3 5 4 6
x
y
z
Figura 4.9. Distribuição de um domínio entre 6 devices
31
Tabela 4.1. Estrutura de dados DataStruct
Descrição Tipo Variável
Velocidade float *u
Sólidos bool *solid
Sólidos sem fronteira com o fluído bool *solid_bound
Densidades atuais do fluido em cada direção
float *fa (D2Q9: a = 0...8; D3Q19: a = 0...18)
Densidades novas do fluido em cada direção
float *fa_new (D2Q9: a = 0...8; D3Q19: a = 0...18)
Bordas compartilhadas float *border_fa (D2Q9: a = 1,3,5,6,7,8; D3Q19: a = 1,2,7...14)
Bordas compartilhadas do vizinho float *nb_border_fa (D2Q9: a = 1,3,5,6,7,8; D3Q19: a = 1,2,7...14)
Tamanho do domínio na GPU para cada dimensão
int width, height, length (3D)
Também é definida uma estrutura SaveStruct que é usada para salvar os
arquivos de saída. A definição dessa estrutura se torna necessária para poder
utilizar Pthreads para realizar essa função, e assim poder continuar processando
dados pela GPU enquanto a CPU salva o arquivo. As variáveis presentes nessa
estrutura são mostrada na tabela abaixo.
Tabela 4.2. Estrutura de dados SaveStruct
Descrição Tipo Variável
Velocidades float *u
Sólidos bool *solid
Nome do arquivo char *name
Passo atual int step
Número do processo MPI int commRank
Número do device int deviceID
Quantidade total de devices int deviceCount
Tamanho do domínio na GPU para cada dimensão
int width, height, length (3D)
A função initialize() é que realiza a alocação de dados na CPU e na
GPU, lê o arquivo de sólidos, calcula os valores das funções de densidade e das
velocidades para condição inicial e, caso especificado o processamento em GPU,
realiza a transferência dos dados iniciais para o device.
32
Caso o programa seja executado em um cluster com vários nós, é também
realizada a alocação de buffers border_fa_buffer, para transferência de dados
das bordas compartilhadas entre os nós do cluster.
4.3 Arquivos de saída
Os arquivos de saída são salvos no formato VTK XML ImageData, sendo que é
necessária a criação de arquivos de dois tipos, paralelo e serial. O tipo serial, de
extensão vti, reúne um conjunto de dados em ponto flutuante, sendo que cada
dado representa a velocidade de um nó lattice. Cada processo MPI salva um arquivo
vti com os dados do domínio relativo a esse processo, sendo a união dos dados
realizada pelo arquivo paralelo. O tipo paralelo possui extensão pvti e nele contém
quais são os arquivos do tipo serial que serão unidos para formar a imagem final,
sendo salvo apenas pelo primeiro processo MPI através da função psave().
Conforme mencionado anteriormente, os arquivos são salvos a partir de uma
estrutura contendo os dados de sólidos e velocidades, sendo criada uma Pthread
que executa a função save() em cada processo MPI. Antes de salvar os arquivos,
os dados de velocidades são transferidos da estrutura DataStruct. Como quem
salva os arquivos é a CPU, enquanto a Pthread estiver sendo executada, o
programa continuará a computar os dados do método lattice Boltzmann
independetemente, já que os dados da estrutura SaveStruct não são influenciados
pelo método até que seja necessário salvar um novo arquivo.
4.4 Propagação
A etapa de propagação é realizada pela função streaming() que apenas
copia os valores da densidade de fa para próximo nó lattice na direção a em
fa_new. Quando este nó não existe, pelo fato de o nó atual estar em alguma
fronteira do grid, é feita a cópia para o nó da fronteira oposta, aplicando a condição
de fronteira periódica ao sistema.
Caso o programa seja executado de forma distribuída, para finalizar o processo
de propagação, é necessário compartilhar os valores de fa_new para os nós que
33
se encontram na fronteira entre devices ou processos MPI. O processo de
compartilhamento de fronteira é descrito nos próximos parágrafos, sendo
apresentadas todas as etapas deste processo na figura 4.6, mostrando o caso de
um domínio divido em dois processos MPI com dois devices em cada. Nesta figura é
demonstrado onde as fronteiras de fa estão alocadas nas outras variáveis, sendo
indicado o número do processo p x , o número do device d x e se o valor da
coordenada x é o inicial ou o final.
Para realizar esse processo, primeiramente é copiada os dados das bordas
que vão ser compartilhadas para a estrutura border_fa através da função
get_border(). Essa cópia se torna necessária para poder realizar a troca de
apenas os dados necessários entre as GPUs e os processos MPI, restringindo ao
máximo o fluxo de dados entre eles, já que esse fluxo se torna um gargalo para o
programa devido às larguras de banda menores.
Após os valores de border_fa estarem completos, é feita a cópia de
border_fa para nb_border_fa do device que vai receber os dados através da
função copy_border_devices(). Essa cópia é realizada através de funções
cuda_MemcpyAsync(), que copiam os dados de forma assíncrona. Essa função é
chamada de forma a utilizar os recursos de endereço de memória unificado (UVA),
sendo informado o tipo de transferência cudaMemcpyDefault.
Caso a execução do programa também esteja distribuída entre nós de um
cluster, é realizada a troca das informações de nb_border_fa() nas fronteiras dos
processos MPI. Para realizar essa troca de dados é utilizada a função
copy_border_nodes(), que copia os dados de nb_border_fa() do device para
os buffers border_fa_buffer no host, realiza chamadas
MPI_Sendrecv_replace(), que faz a troca dos dados dos buffers entre os nós
vizinhos, e transfere novamente os dados para os devices.
Depois os dados de nb_broder_fa são copiados de volta para os seus
lugares em fa_new pela função apply_border(), completando o processo de
propagação.
34
Figura 4.10. Distribuição das fronteiras durante a
propagação em dois nós com dois devices cada
4.5 Colisão
Com os valores das funções de densidade devidamente propagados para
fa_new, pode-se então realizar a etapa de colisão. A colisão consiste em aplicar a
aplicar o termo de colisão nas funções de densidade fa sendo executada através da
função collision().
Existem duas possibilidades de se aplicar a colisão. Se o nó lattice é um sólido
que possue alguma fronteira com o fluído é aplicada a condição de fronteira bounce-
back. Caso o nó seja fluído, são calculados os valores da densidade do fluido de
35
acordo com a equação (4), das velocidades em cada direção e da velocidade total
que será enviada para o arquivo de saída conforme a equação (5). Com esses
valores calculados, é possível obter a função de densidade de equilíbrio pela
equação (8) e aplicá-la em fa através da resolução da equação (6), concluindo o
processo de colisão.
4.6 Finalização
Após executados todos os passos e salvos os arquivos de saída conforme
definido pelo arquivo de inicialização, é necessário desalocar as memórias dos
devices e hosts. As memórias dos devices são desalocadas pela função
cudaFree_data() que desaloca todos os ponteiros definidos em dataStruct.
Para terminar é finalizado os processos MPI através da chamada
MPI_Finalize().
36
Capítulo 5
Método de Ensaio
Para avaliar o desempenho da execução do simulador, foram realizados testes
para diferentes quantidades de GPUs e tamanhos de domínios para os casos D2Q9
e D3Q19.
O cluster utilizado para o teste possui 16 GPUs Geforce GTX 580, com 1,5 GB
de memória cada e 1,58 TFLOPs de capacidade de processamento cada,
totalizando 24 GB de memória e 25,3 TFLOPs. Os testes foram realizados para 1, 2,
4, 8, 12 e 16 GPUs. Para comparação, também foram executados testes utilizando
os quatro núcleos de um processador Intel Core i7 950 a 3,06 GHz que possui cerca
de 49 GFLOPs de capacidade de processamento, cerca de 32 vezes menor que
uma GPU.
Os tamanhos de domínios foram escolhidos de forma a comparar o ganho de
desempenho obtido desde um domínio pequeno, que utiliza pouca memória, até um
domínio que utiliza praticamente todos os 18 GB disponível de memória. Todos os
tamanhos de domínio utilizados e a quantidade de memória ocupada são
demonstrados nas tabelas 5.1 e 5.2.
Tabela 5.1. Tamanhos dos domínios utilizados nos testes 2D
Tamanho do Domínio
Memória Utilizada (MB)
600x128 6
1200x256 23
2400x512 91
4800x1024 366
9600x2048 1463
12800x3072 2925
19200x4096 5850
25600x6144 11700
32960x7168 17574
38400x8192 23400
37
Tabela 5.2. Tamanhos dos domínios utilizados nos testes 3D
Tamanho do Domínio
Memória Utilizada (MB)
128x24x24 11
192x36x36 38
256x52x52 104
384x76x76 334
640x120x120 1389
832x148x148 2746
1024x188x188 5454
1280x240x240 11110
1408x280x280 16635
1536x310x310 22244
38
Capítulo 6
Resultados
Os resultados dos testes, dados em passos de tempo processados por
segundo, são demonstrados na tabela 6.1 para o modelo D2Q9 e na tabela 6.2 para
o D3Q19. Também são apresentados gráficos em escala logarítmica relacionando a
velocidade de execução com o tamanho do domínio nas figuras 6.1 e 6.2.
Tabela 6.1. Velocidade de execução dos testes (D2Q9)
Tamanho do Domínio
CPU (ts/s)
1 GPU (ts/s)
2 GPUs (ts/s)
4 GPUs (ts/s)
8 GPUs (ts/s)
12 GPUs (ts/s)
16 GPUs (ts/s)
600x128 850 11980 3360 1830 485 - -
1200x256 91,1 3148 2270 1700 466 - 494
2400x512 29,7 755 983 1152 458 640 673
4800x1024 9 175 298 507 486 500 534
9600x2048 1,84 43,1 80 162 218 270 333
12800x3072 0,75 - 40,6 74 120 134,3 203
19200x4096 - - - 39,6 65,06 93,25 113
25600x6144 - - - - 34,7 48,4 57
32960x7168 - - - - - 31,8 38,3
38400x8192 - - - - - - 32,9
Tabela 6.2. Velocidade de execução dos testes (D3Q19)
Tamanho do Domínio
CPU (ts/s)
1 GPU (ts/s)
2 GPUs (ts/s)
4 GPUs (ts/s)
8 GPUs (ts/s)
12 GPUs (ts/s)
16 GPUs (ts/s)
128x24x24 302 4556 1844 1099 - - -
192x36x36 129 1395 1201 975 - - -
256x52x52 31,2 505 627 657 310 - -
384x76x76 10 154 248 334 194 192 -
640x120x120 1,74 36,9 66,6 111 80,4 94,3 91,2
832x148x148 0,79 - 33,4 64,6 35,6 41,1 47,6
1024x188x188 - - - 34,2 22,1 31,6 27,7
1280x240x240 - - - - 10,8 15,6 17,9
1408x280x280 - - - - - 10,4 13,6
1536x310x310 - - - - - - 9,82
39
Figura 6.1. Velocidade de execução dos testes em relação à área do
domínio para diferentes sistemas (D2Q9)
Figura 6.2. Velocidade de execução dos testes em relação à área do
domínio para diferentes sistemas (D3Q19)
Analisando o gráfico do caso 2D percebe-se que para um domínio de até
70.000 lu2 não há aumento de velocidade em se usar mais de uma GPU, e que para
domínios de até 600.000 lu2 não há aumento de velocidade em se utilizar mais de
um nó. Também se observa que adicionando GPUs para executar o programa,
possibilita a execução de domínios cada vez maiores, ampliando a gama de
problemas que podem ser resolvidos.
40
Para o caso 3D nota-se que para domínios a partir de 40.000 lu3 passou-se a
obter ganho de velocidade em se adicionar mais GPUs, mas em nenhum momento
houve aumento de velocidade em se utilizar mais de um nó, limitando seu uso
apenas para domínios em que necessite de mais de 6GB de memória. Este fato
ocorre pois a quantidade de dados das fronteiras compartilhadas é muito maior no
caso 3D que no 2D, exigindo um maior tráfego de dados pela rede, que possui um
tempo de resposta relativamente alto e uma largura de banda de 1GB/s, 8 vezes
menor que a largura de banda entre as GPUs.
A partir dos dados obtidos, também foram feitos alguns gráficos para análises
de fatores mais pontuais. O primeiro gráfico é apresentado na figura 6.3, mostrando
o ganho de velocidade do uso de até 4 GPUs em um mesmo nó em relação a
execução na CPU para os diversos domínios testados no caso 2D. Analisando o
gráfico, observa-se que a execução em uma GPU chegou a ser até 35 vezes mais
rápida que na CPU, ficando dentro da expectativa da GPU possuir um poder de
processamento cerca de 32 vezes maior que a CPU utilizada. Para o caso de 2
GPUs, a execução foi 54 vezes mais rápida e para 4 GPUs, 99 vezes,
demonstrando que mesmo com a troca de dados das fronteiras compartilhadas,
pode-se obter grandes ganhos de velocidades adicionando mais devices. Um gráfico
similar é apresentado para o caso 3D na figura 6.4, indicando que a execução
chegou a ser 21 vezes mais rápida utilizando 1 GPU e até 82 vezes maior utilizando
4 GPUs, obtendo um desempenho relativamente menor em relação ao caso 2D.
41
Figura 6.3. Ganho de velocidade de execução em relação a CPU (D2Q9)
Figura 6.4. Ganho de velocidade de execução em relação a CPU (D3Q19)
Os gráficos apresentados nas figuras 6.5 e 6.6 demonstram o ganho em se
utilizar até 12 GPUs em relação a mínimo possível de GPUs utilizáveis para um
determinado tamanho de domínio. Também são mostrados gráficos avaliando a
eficiência da execução do programa em relação a menor quantidade de GPUs
utilizáveis para os domínios, nas figuras 6.7 e 6.8. A partir destes gráficos nota-se
que quanto maior o domínio, menor é a perda de eficiência em se utilizar mais GPU,
chegando sempre a valores maiores que 90%, isso ocorre porque o tamanho da
fronteira compartilhada se torna relativamente menor em relação à área do domínio
processada nas GPUs conforme o domínio aumenta. Sendo assim é recomendado
sempre minimizar o máximo possível o tamanho da borda compartilhada antes de
executar um problema.
42
Figura 6.5. Ganho de velocidade em relação ao
mínimo necessário de GPUs (D2Q9)
Figura 6.6. Ganho de velocidade em relação ao
mínimo necessário de GPUs (D3Q19)
43
Figura 6.7.Eficiência do processamento em relação
ao mínimo necessário de GPUs (D2Q9)
Figura 6.8. Eficiência do processamento em relação
ao mínimo necessário de GPUs (D3Q19)
Abaixo são apresentados gráficos que relacionam o ganho de velocidade e a
eficiência de até quatro nós de um cluster em relação ao mínimo possível de nós
para execução dos domínios. Pela análise dos gráficos para o caso D2Q9, percebe-
se que não se consegue uma eficiência maior que 80% quando se utiliza mais de
um nó, devido a menor largura de banda na transferência dos dados entre os nós e
da necessidade da transferência dos dados para o host para realizar a troca de
44
informação entre os processos MPI. Para o caso do domínio de 25600x6144, que
não pode ser executado em apenas um nó devido a disponibilidade de memória, a
eficiência da utilização de 3 nós chega aos 90% em relação a 2 nós, já que em
ambos os casos existe a necessidade de copiar os dados para o host. Já para o
caso D3Q19, percebe-se melhor o quanto se perde de velocidade ao se utilizar mais
de um nó, chegando-se ao máximo de 30% de eficiência. Porém em domínios que
necessitem de mais de um nó, a utilização de um terceiro nó obteve uma eficiência
de cerca de 90% justificando o uso de vários nós para esses tamanhos de domínio.
Figura 6.9. Ganho de velocidade em relação ao mínimo necessário de nós (D2Q9)
45
Figura 6.10. Ganho de velocidade em relação ao mínimo necessário de nós (D3Q19)
Figura 6.11. Eficiência do processamento em relação
ao mínimo necessário de nós (D2Q9)
46
Figura 6.12. Eficiência do processamento em relação
ao mínimo necessário de nós (D3Q19)
Outro item avaliado foi a diferença entre se usar um nó com 4 GPUs, dois nós
com duas GPUs cada e 4 nós com uma GPU cada. Nos gráficos abaixo são
mostradas a velocidade de execução e a eficiência para os diferentes tamanhos de
domínio para os dois casos analisados, demonstrando que para domínios
suficientemente grandes, a perda de eficiência com a utilização de dois ou mais nós
se torna mínima para o caso D2Q9, mas é significativa no caso D3Q19, que realiza
uma quantidade muito maior de troca de informações entre os nós.
47
Figura 6.13. Velocidade de execução dos testes em relação à área do domínio
comparando 1 nó com 4 devices e 2 nós com 2 devices cada (D2Q9)
Figura 6.14. Velocidade de execução dos testes em relação à área do domínio
comparando 1 nó com 4 devices e 2 nós com 2 devices cada (D3Q19)
48
Figura 6.15. Eficiência do processamento em relação a 1 nó com 4 devices (D2Q9)
Figura 6.16 Eficiência do processamento em relação a 1 nó com 4 devices (D3Q19)
Outro aspecto importante para avaliação do desempenho do programa é o
tempo para salvar o arquivo de saída, que influencia no tempo de execução total do
programa. Abaixo é apresentado o gráfico relacionando o tempo para salvar um
arquivo de saída com a quantidade de nós lattice do domínio para os casos 2D e 3D.
49
Figura 6.17. Tempo para salvar arquivo de saída
em relação a área do domínio (D2Q9)
Figura 6.18 Tempo para salvar arquivo de saída
em relação a área do domínio (D3Q19)
Pela análise dos gráficos conclui-se que a utilização de mais de um nó
proporciona um menor tempo para saída ser salva, devido à distribuição dos
domínios entre os nós e a largura de banda maior que mais nós proporcionam, já
que cada nó salva um arquivo diferente, sendo que com 2 nós os arquivos são
salvos 2 vezes mais rápidos e com 3 nós, 2,5 vezes mais rápido. Para quatro nós
50
observa-se que a saída já é salva em um tempo maior que com três nós, devido a
um maior tempo de acesso que quatro nós acessando um disco rígido ao mesmo
tempo proporciona.
Para o caso da CPU percebe-se uma vantagem em relação a um device,
devido aos dados serem distribuídos em quatro núcleos e já estarem sendo
executados pela CPU, porém vê-se uma diferença, que não é vista pelas GPUs,
entre o caso 2D e 3D, sendo que no caso 3D o tempo foi 25% menor.
51
Capítulo 7
Conclusão
A utilização de computação paralela com placas de vídeo demonstrou-se muito
eficiente na resolução de problemas como o do método lattice Boltzmann, sendo
muito superior a computação tradicional, chegando a executar o método até 30
vezes mais rápido em relação ao processador utilizando apenas uma GPU. Com a
distribuição do domínio em várias GPUs, além de se obter velocidades de
processamento maiores, aumenta-se a quantidade de memória disponível para a
resolução do método, possibilitando a execução em domínios maiores.
Porém o ganho de velocidade alcançado aumentando a quantidade de GPUs
depende do tamanho do domínio processado, sendo que para domínios
relativamente pequenos, perde-se desempenho adicionando mais GPUs, devido ao
tempo gasto para as trocas de dados entre as GPUs e os nós não compensar. Por
isso é importante definir a quantidade de GPUs utilizadas para resolver um
determinado problema de acordo com o tamanho de seu domínio.
Também foi observado que para o caso D3Q19 não se obtém um aumento de
velocidade ao se utilizar mais de um nó, devido à baixa largura de banda disponível
entre os nós do cluster, justificando seu uso apenas para domínios que necessitam
mais memória que a disponível em um nó. Uma das possibilidades de melhorar esse
desempenho é através do aumento da largura de banda da rede ethernet utilizada,
que pode ser dobrada para 2GB/s utilizando os dois canais que cada nó possui em
paralelo. Há margem, ainda, para melhoria de desempenho pelo refinamento do
software, utilizando técnicas mais eficientes de programação.
Este trabalho terá continuidade como tema de dissertação de mestrado do
autor, também sob orientação do Prof. Luiz Otávio.
52
Referência Bibliográfica
[1] NVIDIA, NVIDIA CUDA C Programming Guide 4.2. 2012.
[2] SANDER, Jason. CUDA by Example: An Introduction to General-Purpose GPU
Programming. nVidia, 2011.
[3] NVIDIA, CUDA C Best Practices Guide 4.1. 2012.
[4] NVIDIA, Fermi Compute Architecture Whitepaper 1.1. 2009.
[5] TREVISAN, J. Programação Paralela Utilizando MPI: aspectos práticos.
GRUCAD, UFSC, Florianópolis, 2005. Disponível em:
<http://www.grucad.ufsc.br/julio/doc/mpi.htm>. Acesso em: mai/2012.
[6] SUKOP, Michael. Lattice Boltzmann Modeling: An Introduction for Geoscientists
and Engineers. 2ª edição, Springer-Verlag, 2007.
[7] MOHAMED, A. A.. Lattice Boltzmann Method: Fundamentals and Engineering
Applications with Computer Codes. 1ª edição, Springer-Verlag, 2011.
[8] HE, X. and LUO, L. S.. Theory of the lattice Boltzmann method: From the
Boltzmann equation to the lattice Boltzmann equation. Phys. Rev. E, 56(6):6811–
6817, 1997.
[9] CHIRILA, Dragos. Introduction to Lattice Boltzmann Methods. Alfred Wegener
Institute, 2010.
[10] HECHT, M.; HARTING, J.. Implementation of on-site velocity boundary
conditions for D3Q19 lattice Boltzmann simulations. Journal of Statistical Mechanics:
Theory and Experiment, 28 de Janeiro de 2010.
[11] RAUBER, T; GUDULA, R.. Parallel Programming for Multicore and Cluster
Systems. 2ª edição, Springer-Verlag, 2010.
53
Apêndice
latibol.h:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* This header file defines the memory structure that will be allocated in each device.
* Each variable is an array of the domain total dimension, except the border variables.
**************************************************************************************************/
#ifndef LB_H
#define LB_H
typedef struct {
// Velocity [lu/ts]
float *u;
// Solid node
bool *solid, *solid_bound;
// Density functions
float *f0, *f1, *f2, *f3, *f4;
float *f5, *f6, *f7, *f8;
// Density function for the next time step
float *f0_new, *f1_new, *f2_new, *f3_new, *f4_new;
float *f5_new, *f6_new, *f7_new, *f8_new;
// Border Density Functions
float *border_f1, *border_f5, *border_f8;
float *border_f3, *border_f6, *border_f7;
// Neighbor border density function
float *nb_border_f1, *nb_border_f5, *nb_border_f8;
float *nb_border_f3, *nb_border_f6, *nb_border_f7;
// Size of the domain
int width, height;
// Stream
cudaStream_t stream;
} DataStruct;
typedef struct {
// Velocity [lu/ts]
float *u;
// Solid node
bool *solid, *solid_bound;
// Density functions
float *f0, *f1, *f2, *f3, *f4;
float *f5, *f6, *f7, *f8, *f9;
float *f10, *f11, *f12, *f13, *f14;
float *f15, *f16, *f17, *f18;
// Density function for the next time step
float *f0_new, *f1_new, *f2_new, *f3_new, *f4_new;
float *f5_new, *f6_new, *f7_new, *f8_new, *f9_new;
float *f10_new, *f11_new, *f12_new, *f13_new, *f14_new;
float *f15_new, *f16_new, *f17_new, *f18_new;
// Border Density Functions
float *border_f1, *border_f7, *border_f8, *border_f9, *border_f10;
float *border_f2, *border_f11, *border_f12, *border_f13, *border_f14;
// Neighbor border density function
float *nb_border_f1, *nb_border_f7, *nb_border_f8, *nb_border_f9, *nb_border_f10;
float *nb_border_f2, *nb_border_f11, *nb_border_f12, *nb_border_f13, *nb_border_f14;
// Size of the domain
int width, height, length;
// Stream
cudaStream_t stream;
54
} DataStruct3d;
typedef struct {
// Velocity [lu/ts]
float *u;
// Solid node
bool *solid, *solid_bound;
// Density functions
float *f0, *f1, *f2, *f3, *f4;
float *f5, *f6, *f7, *f8;
// Density function for the next time step
float *f0_new, *f1_new, *f2_new, *f3_new, *f4_new;
float *f5_new, *f6_new, *f7_new, *f8_new;
// Border Density Functions
float *border_f1, *border_f5, *border_f8;
float *border_f3, *border_f6, *border_f7;
// Size of the domain
int width, height;
} DataStructCpu;
typedef struct {
// Velocity [lu/ts]
float *u;
// Solid node
bool *solid, *solid_bound;
// Density functions
float *f0, *f1, *f2, *f3, *f4;
float *f5, *f6, *f7, *f8, *f9;
float *f10, *f11, *f12, *f13, *f14;
float *f15, *f16, *f17, *f18;
// Density function for the next time step
float *f0_new, *f1_new, *f2_new, *f3_new, *f4_new;
float *f5_new, *f6_new, *f7_new, *f8_new, *f9_new;
float *f10_new, *f11_new, *f12_new, *f13_new, *f14_new;
float *f15_new, *f16_new, *f17_new, *f18_new;
// Border Density Functions
float *border_f1, *border_f7, *border_f8, *border_f9, *border_f10;
float *border_f2, *border_f11, *border_f12, *border_f13, *border_f14;
// Size of the domain
int width, height, length;
} DataStruct3dCpu;
typedef struct {
// Velocity [lu/ts]
float *u;
// Solid node
bool *solid;
// Name
char *name;
// ID variables
int step, commRank, deviceID, deviceCount;
// Size of the domain
int width, height, length;
} SaveStruct;
#endif
55
latibol.cu:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* Main function
**************************************************************************************************/
// Include standard libraries
#include <stdio.h>
#include <stdlib.h>
#include <cutil.h>
#include <cuda_runtime_api.h>
#include <time.h>
#include <cmath>
#include <mpi.h>
#include <pthread.h>
#include <error.h>
#include <iostream>
using std::cout;
using std::cerr;
using std::endl;
// Include functions files
#include "latibol.h"
#include "initialize.h"
#include "save.h"
#include "cudaFree_data.h"
#include "streaming.h"
#include "get_border.h"
#include "copy_border.h"
#include "apply_border.h"
#include "collision.h"
// Define CUDA block width and height
#define BLOCK_X 32
#define BLOCK_Y 4
#define BLOCK_Z 1
// Error handling macros
#define MPI_CHECK(call) \
if((call) != MPI_SUCCESS) { \
cerr << "MPI error calling \""#call"\"\n"; \
my_abort(-1); }
// Shut down MPI cleanly if something goes wrong
void my_abort(int err)
{
cout << "\nMPI error!\n";
MPI_Abort(MPI_COMM_WORLD, err);
}
int main(int argc, char **argv)
{
// Initialize MPI state
MPI_CHECK(MPI_Init(&argc, &argv));
// Get our MPI node number and node count
int commSize, commRank;
MPI_CHECK(MPI_Comm_size(MPI_COMM_WORLD, &commSize));
MPI_CHECK(MPI_Comm_rank(MPI_COMM_WORLD, &commRank));
MPI_Status Stat;
pthread_t thread;
/**********************************************************************************************
* Create variables
**********************************************************************************************/
// Inputs
float tau, tau_inv, ux, uy, uz, rho, viscosity;
int length, width, height;
int step, iterations;
// Number of devices
int deviceCount;
// Time variables
double t, t_start, t_last, t_save;
// Counters
int i, j, k;
56
// Weights for equilibrium distribution function
float w0, w1, w2;
// Auxiliary
char *temp;
int left, right;
int last, sysDevs;
int read;
bool gpu;
bool is_3d;
int img_width, img_height;
dim3 grid, block;
// Files variables
FILE *parameters, *input, *out;
// Devices data structure
DataStruct *data;
DataStruct3d *data_3d;
DataStructCpu *data_cpu;
DataStruct3dCpu *data_3d_cpu;
SaveStruct *saveData;
// Border buffers
float *border_f1_buf, *border_f2_buf, *border_f3_buf, *border_f5_buf, *border_f6_buf;
float *border_f7_buf, *border_f8_buf, *border_f9_buf, *border_f10_buf;
float *border_f11_buf, *border_f12_buf, *border_f13_buf, *border_f14_buf;
/**********************************************************************************************
* Read input files
**********************************************************************************************/
read = 0;
t_start = MPI_Wtime();
// Read the parameters from 'parameters.ini' file
parameters=fopen("parameters.ini","r");
if (parameters == NULL) {printf("\nparameters.ini not found\n\n"); return 0;}
read=fscanf(parameters, "%d\n", &deviceCount) + read;
read=fscanf(parameters, "%d\n", &length) + read;
read=fscanf(parameters, "%f\n", &ux) + read;
read=fscanf(parameters, "%f\n", &uy) + read;
read=fscanf(parameters, "%f\n", &uz) + read;
read=fscanf(parameters, "%f\n", &viscosity) + read;
read=fscanf(parameters, "%f\n", &rho) + read;
read=fscanf(parameters, "%d\n", &iterations) + read;
read=fscanf(parameters, "%d\n", &step) + read;
fclose(parameters);
if (read != 9) {printf("\nerror: verify parameters.ini\n\n"); return 0;}
// Define GPU or CPU mode
if (deviceCount > 0) gpu = 1;
else gpu = 0;
// Verify if the system has enough devices
if (gpu)
{
CUDA_SAFE_CALL( cudaGetDeviceCount( &sysDevs ) );
if (sysDevs < deviceCount) {printf("\ninsufficient devices: verify parameters.ini\n\n"); return 0;}
}
if (length >= BLOCK_Z && length > 1) is_3d = 1;
else is_3d = 0;
// Calculate tau and its inverse
tau = 3.*viscosity/rho + 0.5;
tau_inv = 1./tau;
// Calculate constants
if (is_3d)
{
w0 = 12.f/36.f;
w1 = 2.f/36.f;
w2 = 1.f/36.f;
}
else
{
w0 = 16.f/36.f;
w1 = 4.f/36.f;
w2 = 1.f/36.f;
}
// Allocate temporary variable that will read from input file
temp = (char *)malloc(2*sizeof(char));
// Get the lattice width and height for one device from 'input00.bmp'
// monochromatic bitmap file
img_width=0;
57
img_height=0;
input = fopen("input.bmp","r");
// Read the width
if (input == NULL) {printf("\ninput.bmp not found\n\n"); return 0;}
fseek(input, 0x12, SEEK_SET);
read=fscanf(input, "%c", &temp[0]) + read;
read=fscanf(input, "%c", &temp[1]) + read;
for (i=0; i<8; i++) {
if ( ( (int)temp[0] & (1 << i) ) >> i ) img_width = img_width + pow(2.0, i);
if ( ( (int)temp[1] & (1 << i) ) >> i ) img_width = img_width + pow(2.0, i)*256;
}
// Read the height
fseek(input, 0x16, SEEK_SET);
read=fscanf(input, "%c", &temp[0]) + read;
read=fscanf(input, "%c", &temp[1]) + read;
for (i=0; i<8; i++) {
if ( ( (int)temp[0] & (1 << i) ) >> i ) img_height = img_height + pow(2.0, i);
if ( ( (int)temp[1] & (1 << i) ) >> i ) img_height = img_height + pow(2.0, i)*256;
}
if (read != 13) {printf("\nerror: verify input.bmp\n\n"); return 0;}
// Close the file and free temporary variable
fclose( input );
free( temp );
// Get the number of devices
if (commRank == 0)
{
// Write all the parameters at the screen
printf ("\nnodes = %d\n", commSize);
printf ("devices = %d\n", deviceCount);
}
// Calculate the size of the domain
if (!gpu) deviceCount = 1;
width = (img_width-1 + commSize*deviceCount) / (commSize*deviceCount);
width = (width/BLOCK_X)*BLOCK_X;
height = (img_height/BLOCK_Y)*BLOCK_Y;
length = (length/BLOCK_Z)*BLOCK_Z;
if (length <= BLOCK_Z) length = 1;
if (commRank == 0)
{
printf ("width = %d\n", (width-1)*commSize*deviceCount+1);
printf ("height = %d\n", height);
printf ("length = %d\n", length);
printf ("ux = %f\n", ux);
printf ("uy = %f\n", uy);
printf ("uz = %f\n", uz);
printf ("viscosity = %f\n", viscosity);
printf ("rho = %f\n", rho);
printf ("tau = %f\n", tau);
printf ("iterations = %d\n", iterations);
printf ("step = %d\n\n", step);
}
/**********************************************************************************************
* Initialize devices
**********************************************************************************************/
// Initialize data structure and allocate devices memories
if (is_3d)
if (gpu)
{
saveData = (SaveStruct *) malloc (deviceCount*sizeof(SaveStruct));
data_3d = (DataStruct3d *)malloc(deviceCount*sizeof(DataStruct3d ));
initialize_3d( data_3d, saveData, deviceCount, ux, uy, uz, rho, w0, w1, w2,
width, height, length, img_width, commRank );
}
else
{
saveData = (SaveStruct *) malloc (sizeof(SaveStruct));
data_3d_cpu = (DataStruct3dCpu *)malloc(sizeof(DataStruct3dCpu ));
initialize_3d_cpu( data_3d_cpu, saveData, ux, uy, uz, rho, w0, w1, w2,
width, height, length, img_width, commRank );
}
else
if (gpu)
{
saveData = (SaveStruct *) malloc (deviceCount*sizeof(SaveStruct));
data = (DataStruct *)malloc(deviceCount*sizeof(DataStruct ));
initialize( data, saveData, deviceCount, ux, uy, rho, w0, w1, w2, width, height, img_width,
commRank );
}
else
{
58
saveData = (SaveStruct *) malloc (sizeof(SaveStruct));
data_cpu = (DataStructCpu *)malloc(sizeof(DataStructCpu ));
initialize_cpu ( data_cpu, saveData, ux, uy, rho, w0, w1, w2, width, height, img_width,
commRank );
}
if (is_3d)
{
// Calculate the grid and block dimension
grid = dim3(width/BLOCK_X, height/BLOCK_Y, length/BLOCK_Z);
block = dim3(BLOCK_X, BLOCK_Y, BLOCK_Z);
// Allocate CPU border buffers
border_f1_buf = (float *)malloc(height*length*sizeof(float));
border_f7_buf = (float *)malloc(height*length*sizeof(float));
border_f8_buf = (float *)malloc(height*length*sizeof(float));
border_f9_buf = (float *)malloc(height*length*sizeof(float));
border_f10_buf= (float *)malloc(height*length*sizeof(float));
border_f2_buf = (float *)malloc(height*length*sizeof(float));
border_f11_buf= (float *)malloc(height*length*sizeof(float));
border_f12_buf= (float *)malloc(height*length*sizeof(float));
border_f13_buf= (float *)malloc(height*length*sizeof(float));
border_f14_buf= (float *)malloc(height*length*sizeof(float));
}
else
{
// Calculate the grid and block dimension
grid = dim3(width/BLOCK_X, height/BLOCK_Y);
block = dim3(BLOCK_X, BLOCK_Y);
// Allocate CPU border buffers
border_f1_buf = (float *)malloc(height*sizeof(float));
border_f5_buf = (float *)malloc(height*sizeof(float));
border_f8_buf = (float *)malloc(height*sizeof(float));
border_f3_buf = (float *)malloc(height*sizeof(float));
border_f6_buf = (float *)malloc(height*sizeof(float));
border_f7_buf = (float *)malloc(height*sizeof(float));
}
out=fopen("out.txt","w");
/**********************************************************************************************
* Run Lattice Boltzmann Method and save output files
**********************************************************************************************/
for(i=0; i < (iterations/step)+1; i++)
{
// Save data file and show saving time
if (gpu) CUDA_SAFE_CALL( cudaDeviceSynchronize() );
t = MPI_Wtime() - t_start;
if (commRank == 0) psave(width, height, length, i, deviceCount, commSize);
for (k=0; k < deviceCount; k++)
{
// Wait previous thread ends
if (i != 0) pthread_join (thread, NULL);
// Copy the result to saveData
if (gpu)
{
CUDA_SAFE_CALL( cudaSetDevice( k ) );
if (is_3d) {CUDA_SAFE_CALL( cudaMemcpyAsync(saveData[k].u, data_3d[k].u,
sizeof(float)*width*height*length, cudaMemcpyDefault, data_3d[k].stream));}
else {CUDA_SAFE_CALL( cudaMemcpyAsync(saveData[k].u, data[k].u,
sizeof(float)*width*height, cudaMemcpyDefault, data[k].stream));}
}
else
{
if (is_3d) for (j=0; j < width*height*length; j++) saveData->u[j] = data_3d_cpu->u[j];
else for (j=0; j < width*height; j++) saveData->u[j] = data_cpu->u[j];
}
saveData[k].step = i;
saveData[k].deviceID = k;
saveData[k].deviceCount = deviceCount;
// Execute the Save Pthread
pthread_create (&thread, NULL, save, &saveData[k]);
if (i == iterations/step) pthread_join (thread, NULL);
}
t_save = MPI_Wtime() - t_start - t;
t = MPI_Wtime() - t_start;
if (commRank == 0)
{
printf("- t_save=%.2f ms ", t_save*1000);
fprintf(out, "r%04d.pvti - t_save=%.2f ms ", i, t_save*1000);
if (i == 0)
{
printf ("(%.2f s) \n", t);
fprintf (out, "(%.2f s) \n", t);
59
}
else
{
printf ("(%.2fs - %.2f tsps) \n", t, step/((float)(t-t_last-t_save)));
fprintf (out, "(%.2fs - %.2f tsps) \n", t, step/((float)(t-t_last-t_save)));
}
}
t_last=t;
// Run 'step' times without saving
if (i < (iterations/step)) for (j=0; j < step; j++)
{
// Run streaming process for each device
if (gpu) for (k=0; k < deviceCount; k++)
{
CUDA_SAFE_CALL( cudaSetDevice( k ) );
if (is_3d) streaming_3d( &data_3d[k], grid, block );
else streaming ( &data[k], grid, block );
}
else if (is_3d) streaming_3d_cpu ( data_3d_cpu );
else streaming_cpu ( data_cpu );
if (deviceCount > 1 || commSize > 1)
{
// Get the border for each device
if (gpu) for (k=0; k < deviceCount; k++)
{
CUDA_SAFE_CALL( cudaSetDevice( k ) );
if (is_3d) get_border_3d ( &data_3d[k], grid, block );
else get_border ( &data[k], grid, block );
}
else if (is_3d) get_border_3d_cpu ( data_3d_cpu );
else get_border_cpu ( data_cpu );
// Copy border data between neighbor devices
if (gpu)
{
if (is_3d) for (k = 0; k < deviceCount; k++)
{
if (deviceCount == 1) { left = 0; right = 0; last = 0; }
else
{
if (k > 0 && k < (deviceCount-1)) { left = k-1; right = k+1; }
if (k == 0) { left = deviceCount-1; right = k+1; }
if (k == deviceCount-1) { left = k-1; right = 0; }
}
CUDA_SAFE_CALL( cudaSetDevice( k ) );
copy_border_3d_devices(data_3d[k].nb_border_f1, data_3d[k].nb_border_f7,
data_3d[k].nb_border_f8,
data_3d[k].nb_border_f9, data_3d[k].nb_border_f10,
data_3d[k].nb_border_f2, data_3d[k].nb_border_f11,
data_3d[k].nb_border_f12,
data_3d[k].nb_border_f13, data_3d[k].nb_border_f14,
data_3d[left].border_f1, data_3d[left].border_f7,
data_3d[left].border_f8,
data_3d[left].border_f9, data_3d[left].border_f10,
data_3d[right].border_f2, data_3d[right].border_f11,
data_3d[right].border_f12,
data_3d[right].border_f13, data_3d[right].border_f14,
height, length, data_3d[k].stream);
}
else for (k = 0; k < deviceCount; k++)
{
if (deviceCount == 1) { left = 0; right = 0; last = 0; }
else
{
if (k > 0 && k < (deviceCount-1)) { left = k-1; right = k+1; }
if (k == 0) { left = deviceCount-1; right = k+1; }
if (k == deviceCount-1) { left = k-1; right = 0; }
}
CUDA_SAFE_CALL( cudaSetDevice( k ) );
copy_border_devices(data[k].nb_border_f1, data[k].nb_border_f5,
data[k].nb_border_f8,
data[k].nb_border_f3, data[k].nb_border_f6,
data[k].nb_border_f7,
data[left].border_f1, data[left].border_f5,
data[left].border_f8, data[right].border_f3,
data[right].border_f6, data[right].border_f7,
height, data[k].stream);
}
}
// Copy border data between neighbor nodes
60
if (commRank == 0) { left = commSize-1; right = commRank+1; }
if (commRank == (commSize-1)) { left = commRank-1; right = 0; }
if (commRank > 0 && commRank < (commSize-1)) { left = commRank-1; right = commRank+1; }
last = deviceCount-1;
if (gpu) CUDA_SAFE_CALL( cudaDeviceSynchronize() );
if (commSize > 1)
{
if (gpu)
{
if (is_3d)
{
CUDA_SAFE_CALL( cudaSetDevice( 0 ) );
copy_border_3d_nodes( border_f1_buf, border_f7_buf, border_f8_buf,
border_f9_buf, border_f10_buf,
data_3d[0].nb_border_f1, data_3d[0].nb_border_f7,
data_3d[0].nb_border_f8,
data_3d[0].nb_border_f9, data_3d[0].nb_border_f10,
height, length, right, left, Stat, data_3d[0].stream);
CUDA_SAFE_CALL( cudaSetDevice( last ) );
copy_border_3d_nodes( border_f2_buf, border_f11_buf, border_f12_buf,
border_f13_buf, border_f14_buf,
data_3d[last].nb_border_f2, data_3d[last].nb_border_f11,
data_3d[last].nb_border_f12, data_3d[last].nb_border_f13,
data_3d[last].nb_border_f14,
height, length, left, right, Stat, data_3d[last].stream);
}
if (!is_3d)
{
CUDA_SAFE_CALL( cudaSetDevice( 0 ) );
copy_border_nodes( border_f1_buf, border_f5_buf, border_f8_buf,
data[0].nb_border_f1, data[0].nb_border_f5,
data[0].nb_border_f8,
height, right, left, Stat, data[0].stream);
CUDA_SAFE_CALL( cudaSetDevice( last ) );
copy_border_nodes( border_f3_buf, border_f6_buf, border_f7_buf,
data[last].nb_border_f3, data[last].nb_border_f6,
data[last].nb_border_f7,
height, left, right, Stat, data[last].stream);
}
}
else
{
if (is_3d)
{
copy_border_3d_nodes_cpu( data_3d_cpu->border_f1, data_3d_cpu->border_f7,
data_3d_cpu->border_f8,
data_3d_cpu->border_f9, data_3d_cpu->border_f10,
height, length, right, left, Stat );
copy_border_3d_nodes_cpu( data_3d_cpu->border_f2, data_3d_cpu->border_f11,
data_3d_cpu->border_f12,
data_3d_cpu->border_f13, data_3d_cpu->border_f14,
height, length, left, right, Stat );
}
else
{
copy_border_nodes_cpu( data_cpu->border_f1, data_cpu->border_f5, data_cpu-
>border_f8,
height, right, left, Stat );
copy_border_nodes_cpu( data_cpu->border_f3, data_cpu->border_f6, data_cpu-
>border_f7,
height, left, right, Stat );
}
}
}
// Apply the border received from neighbor devices
if (gpu) for (k=0; k < deviceCount; k++)
{
CUDA_SAFE_CALL( cudaSetDevice( k ) );
if(is_3d) apply_border_3d ( &data_3d[k], grid, block );
else apply_border ( &data[k], grid, block );
}
else if(is_3d) apply_border_3d_cpu ( data_3d_cpu );
else apply_border_cpu ( data_cpu );
}
// Apply bounce-back, calculate the velocities and the equilibrium distribution function
// Run collision process
if (gpu) for (k=0; k < deviceCount; k++)
{
CUDA_SAFE_CALL( cudaSetDevice( k ) );
if (is_3d) collision_3d( &data_3d[k], w0, w1, w2, tau_inv, grid, block );
61
else collision ( &data[k], w0, w1, w2, tau_inv, grid, block );
}
else if (is_3d) collision_3d_cpu ( data_3d_cpu, w0, w1, w2, tau_inv );
else collision_cpu ( data_cpu, w0, w1, w2, tau_inv );
}
}
/**********************************************************************************************
* Clear devices and exit
**********************************************************************************************/
if (gpu)
if (is_3d)
{
cudaFree_data_3d( data_3d, deviceCount );
free( data_3d );
}
else
{
cudaFree_data( data, deviceCount );
free( data );
}
else if (is_3d) free( data_3d_cpu );
else free( data_cpu );
if (is_3d)
{
free( border_f1_buf );
free( border_f7_buf );
free( border_f8_buf );
free( border_f9_buf );
free( border_f10_buf );
free( border_f2_buf );
free( border_f11_buf );
free( border_f12_buf );
free( border_f13_buf );
free( border_f14_buf );
}
else
{
free( border_f1_buf );
free( border_f5_buf );
free( border_f8_buf );
free( border_f3_buf );
free( border_f6_buf );
free( border_f7_buf );
}
free( saveData );
MPI_CHECK(MPI_Finalize());
if ( commRank == 0 ) printf("\n");
return 0;
}
62
initialize.h:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 28.11.2012
* UNICAMP - Universidade Estadual de Campinas
*
* This header file defines the initialize function. This function allocates the memory in the
* devices, reads the solid inputs from "input.bmp" and passes the initial conditions to
* the devices.
**************************************************************************************************/
#ifndef INIT_H
#define INIT_H
extern "C"
void initialize( DataStruct *data, SaveStruct *saveData, int deviceCount, float ux, float uy,
float rho, float w0, float w1, float w5, int width, int height, int img_width,
int commRank);
extern "C"
void initialize_3d( DataStruct3d *data, SaveStruct *saveData, int deviceCount, float ux, float uy,
float uz, float rho, float w0, float w1, float w5, int width, int height, int length,
int img_width, int commRank);
extern "C"
void initialize_cpu( DataStructCpu *data, SaveStruct *saveData, float ux, float uy,
float rho, float w0, float w1, float w5, int width, int height,
int img_width, int commRank);
extern "C"
void initialize_3d_cpu( DataStruct3dCpu *data, SaveStruct *saveData, float ux, float uy,
float uz, float rho, float w0, float w1, float w5, int width, int height,
int length, int img_width, int commRank);
#endif
initialize.cu:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* Initialize funtion
**************************************************************************************************/
#include <cutil.h>
#include <cuda_runtime_api.h>
#include "latibol.h"
extern "C"
void initialize( DataStruct *data, SaveStruct *saveData, int deviceCount, float ux, float uy,
float rho, float w0, float w1, float w5, int width, int height, int img_width,
int commRank)
{
// Create variables on host side
float *f0, *f1, *f2, *f3, *f4, *f5, *f6, *f7, *f8, *u;
bool *solid, *solid_bound;
// Create auxiliary variables
char temp;
int read = 0;
// Counters
int i, j, k;
int xn, x, xp, yn, y, yp;
// Input file
FILE *input;
// Allocate memories on host side
u = (float *)malloc(width*height*sizeof(float));
solid = (bool *)malloc(width*height*sizeof(bool));
solid_bound = (bool *)malloc(width*height*sizeof(bool));
f0 = (float *)malloc(width*height*sizeof(float));
f1 = (float *)malloc(width*height*sizeof(float));
63
f2 = (float *)malloc(width*height*sizeof(float));
f3 = (float *)malloc(width*height*sizeof(float));
f4 = (float *)malloc(width*height*sizeof(float));
f5 = (float *)malloc(width*height*sizeof(float));
f6 = (float *)malloc(width*height*sizeof(float));
f7 = (float *)malloc(width*height*sizeof(float));
f8 = (float *)malloc(width*height*sizeof(float));
// Calculate initial density functions for initial velocity condition
for (i=0; i<width*height; i++)
{
f0[i] = w0*rho*( 1. - 1.5*(ux*ux+uy*uy) );
f1[i] = w1*rho*( 1. + 3.*(+ux ) + 4.5*(+ux )*(+ux ) - 1.5*(ux*ux+uy*uy) );
f2[i] = w1*rho*( 1. + 3.*( +uy) + 4.5*( +uy)*( +uy) - 1.5*(ux*ux+uy*uy) );
f3[i] = w1*rho*( 1. + 3.*(-ux ) + 4.5*(-ux )*(-ux ) - 1.5*(ux*ux+uy*uy) );
f4[i] = w1*rho*( 1. + 3.*( -uy) + 4.5*( -uy)*( -uy) - 1.5*(ux*ux+uy*uy) );
f5[i] = w5*rho*( 1. + 3.*( ux+uy) + 4.5*( ux+uy)*( ux+uy) - 1.5*(ux*ux+uy*uy) );
f6[i] = w5*rho*( 1. + 3.*(-ux+uy) + 4.5*(-ux+uy)*(-ux+uy) - 1.5*(ux*ux+uy*uy) );
f7[i] = w5*rho*( 1. + 3.*(-ux-uy) + 4.5*(-ux-uy)*(-ux-uy) - 1.5*(ux*ux+uy*uy) );
f8[i] = w5*rho*( 1. + 3.*( ux-uy) + 4.5*( ux-uy)*( ux-uy) - 1.5*(ux*ux+uy*uy) );
u[i] = sqrtf(ux*ux+uy*uy);
}
// Do this for each device
for (i = 0; i < deviceCount; i++)
{
read = 0;
data[i].width = width;
data[i].height = height;
saveData[i].u = (float *)malloc(width*height*sizeof(float));
saveData[i].solid = (bool *)malloc(width*height*sizeof(bool));
saveData[i].name = (char *)malloc(13*sizeof(char));
saveData[i].width = width;
saveData[i].height = height;
saveData[i].length = 1;
saveData[i].commRank = commRank;
// Initialize with no solids (0 state)
for (j=0; j<width*height; j++)
{
solid[j] = 0;
solid_bound[j] = 0;
}
// Open the input file and set at initial data point
input = fopen("input.bmp","r");
for (y=0; y<height; y++)
{
fseek(input, 0x3E + y*img_width/8 + (i+commRank*deviceCount)*width/8, SEEK_SET);
for (x=0; x < width/8; x++)
{
// Read if current point is solid and save to 'solid'
read=fscanf(input, "%c", &temp) + read;
for (k = 8; k > 0; k--)
if ( !( ( (int)temp & (1 << k-1) ) >> k-1 ) )
solid[(x + y*width/8)*8+(8-k)] = 1;
}
}
if (read != width*height/8) printf("\nerror: verify input.bmp\n\n");
// Close input file
fclose(input);
for (j=0; j < width*height; j++) saveData[i].solid[j] = solid[j];
// If all boundaries are solid, solid_bound=1;
for (y=0; y<height; y++)
for (x=1; x<width-1; x++)
{
xn = x-1;
xp = x+1;
// If y is the most top node, the top neighbor will be the most bottom node
yn = (y > 0 ) ? (y-1) : (height-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
yp = (y < (height-1)) ? (y+1) : (0 );
if (solid[x+y*width] && solid[xp+y*width] && solid[x+yp*width] && solid[xn+y*width] &&
solid[x+yn*width] &&
solid[xp+yp*width] && solid[xn+yp*width] && solid[xn+yn*width] && solid[xp+yn*width])
solid_bound[x+y*width] = 1;
}
for (y=0; y<height; y++)
for (x=0; x<width; x++)
{
64
xn = (x > 0 ) ? (x-1) : (width-1);
xp = (x < (width-1) ) ? (x+1) : (0 );
// If y is the most top node, the top neighbor will be the most bottom node
yn = (y > 0 ) ? (y-1) : (height-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
yp = (y < (height-1)) ? (y+1) : (0 );
if (solid_bound[x+y*width] && solid_bound[xp+y*width] && solid_bound[x+yp*width] &&
solid_bound[xn+y*width] && solid_bound[x+yn*width] &&
solid_bound[xp+yp*width] && solid_bound[xn+yp*width] && solid_bound[xn+yn*width] &&
solid_bound[xp+yn*width])
solid_bound[x+y*width] = 1;
}
// Set current device
CUDA_SAFE_CALL( cudaSetDevice( i ) );
// Allocate all memory from data structure to current device
CUDA_SAFE_CALL( cudaMalloc( &data[i].u, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].solid, sizeof(bool)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].solid_bound, sizeof(bool)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f0, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f1, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f2, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f3, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f4, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f5, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f6, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f7, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f8, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f0_new, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f1_new, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f2_new, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f3_new, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f4_new, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f5_new, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f6_new, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f7_new, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f8_new, sizeof(float)*width*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f1, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f5, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f8, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f3, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f6, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f7, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f1, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f5, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f8, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f3, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f6, sizeof(float)*height ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f7, sizeof(float)*height ) );
// Create the stream for current device
CUDA_SAFE_CALL( cudaStreamCreate(&data[i].stream) );
// Copy initial data from host to the device
CUDA_SAFE_CALL( cudaMemcpy( data[i].u, u , sizeof(float)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].solid, solid, sizeof(bool)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].solid_bound, solid_bound, sizeof(bool)*width*height,
cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f0, f0, sizeof(float)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f1, f1, sizeof(float)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f2, f2, sizeof(float)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f3, f3, sizeof(float)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f4, f4, sizeof(float)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f5, f5, sizeof(float)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f6, f6, sizeof(float)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f7, f7, sizeof(float)*width*height, cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f8, f8, sizeof(float)*width*height, cudaMemcpyDefault ) );
}
// Deallocate host memories
free( u );
free( solid );
free( solid_bound );
free( f0 );
free( f1 );
free( f2 );
free( f3 );
free( f4 );
free( f5 );
free( f6 );
free( f7 );
65
free( f8 );
}
extern "C"
void initialize_3d( DataStruct3d *data, SaveStruct *saveData, int deviceCount, float ux, float uy,
float uz, float rho, float w0, float w1, float w7, int width, int height, int length,
int img_width, int commRank)
{
// Create variables on host side
float *f0, *f1, *f2, *f3, *f4, *f5, *f6, *f7, *f8, *f9;
float *f10, *f11, *f12, *f13, *f14, *f15, *f16, *f17, *f18;
float *u;
bool *solid, *solid_bound;
// Create auxiliary variables
char temp;
int read = 0;
// Counters
int i, j, k;
int xn, x, xp, yn, y, yp, zn, z, zp;
// Input file
FILE *input;
// Allocate memories on host side
u = (float *)malloc(width*height*length*sizeof(float));
solid = (bool *)malloc(width*height*length*sizeof(bool));
solid_bound = (bool *)malloc(width*height*length*sizeof(bool));
f0 = (float *)malloc(width*height*length*sizeof(float));
f1 = (float *)malloc(width*height*length*sizeof(float));
f2 = (float *)malloc(width*height*length*sizeof(float));
f3 = (float *)malloc(width*height*length*sizeof(float));
f4 = (float *)malloc(width*height*length*sizeof(float));
f5 = (float *)malloc(width*height*length*sizeof(float));
f6 = (float *)malloc(width*height*length*sizeof(float));
f7 = (float *)malloc(width*height*length*sizeof(float));
f8 = (float *)malloc(width*height*length*sizeof(float));
f9 = (float *)malloc(width*height*length*sizeof(float));
f10 = (float *)malloc(width*height*length*sizeof(float));
f11 = (float *)malloc(width*height*length*sizeof(float));
f12 = (float *)malloc(width*height*length*sizeof(float));
f13 = (float *)malloc(width*height*length*sizeof(float));
f14 = (float *)malloc(width*height*length*sizeof(float));
f15 = (float *)malloc(width*height*length*sizeof(float));
f16 = (float *)malloc(width*height*length*sizeof(float));
f17 = (float *)malloc(width*height*length*sizeof(float));
f18 = (float *)malloc(width*height*length*sizeof(float));
// Calculate initial density functions for initial velocity condition
for (i=0; i<width*height*length; i++)
{
f0[i] = w0*rho*( 1. - 1.5*(ux*ux+uy*uy+uz*uz) );
f1[i] = w1*rho*( 1. + 3.*(+ux ) + 4.5*(+ux )*(+ux ) - 1.5*(ux*ux+uy*uy+uz*uz) );
f2[i] = w1*rho*( 1. + 3.*(-ux ) + 4.5*(-ux )*(-ux ) - 1.5*(ux*ux+uy*uy+uz*uz) );
f3[i] = w1*rho*( 1. + 3.*( +uy ) + 4.5*( +uy )*( +uy ) - 1.5*(ux*ux+uy*uy+uz*uz) );
f4[i] = w1*rho*( 1. + 3.*( -uy ) + 4.5*( -uy )*( -uy ) - 1.5*(ux*ux+uy*uy+uz*uz) );
f5[i] = w1*rho*( 1. + 3.*( +uz) + 4.5*( +uz)*( +uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
f6[i] = w1*rho*( 1. + 3.*( -uz) + 4.5*( -uz)*( -uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
f7[i] = w7*rho*( 1. + 3.*(+ux+uy ) + 4.5*(+ux+uy )*(+ux+uy ) - 1.5*(ux*ux+uy*uy+uz*uz) );
f8[i] = w7*rho*( 1. + 3.*(+ux-uy ) + 4.5*(+ux-uy )*(+ux-uy ) - 1.5*(ux*ux+uy*uy+uz*uz) );
f9[i] = w7*rho*( 1. + 3.*(+ux +uz) + 4.5*(+ux +uz)*(+ux +uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
f10[i]= w7*rho*( 1. + 3.*(+ux -uz) + 4.5*(+ux -uz)*(+ux -uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
f11[i]= w7*rho*( 1. + 3.*(-ux+uy ) + 4.5*(-ux+uy )*(-ux+uy ) - 1.5*(ux*ux+uy*uy+uz*uz) );
f12[i]= w7*rho*( 1. + 3.*(-ux-uy ) + 4.5*(-ux-uy )*(-ux-uy ) - 1.5*(ux*ux+uy*uy+uz*uz) );
f13[i]= w7*rho*( 1. + 3.*(-ux +uz) + 4.5*(-ux +uz)*(-ux +uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
f14[i]= w7*rho*( 1. + 3.*(-ux -uz) + 4.5*(-ux -uz)*(-ux -uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
f15[i]= w7*rho*( 1. + 3.*( +uy+uz) + 4.5*( +uy+uz)*( +uy+uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
f16[i]= w7*rho*( 1. + 3.*( +uy-uz) + 4.5*( +uy-uz)*( +uy-uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
f17[i]= w7*rho*( 1. + 3.*( -uy+uz) + 4.5*( -uy+uz)*( -uy+uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
f18[i]= w7*rho*( 1. + 3.*( -uy-uz) + 4.5*( -uy-uz)*( -uy-uz) - 1.5*(ux*ux+uy*uy+uz*uz) );
u[i] = sqrtf(ux*ux+uy*uy+uz*uz);
}
// Do this for each device
for (i = 0; i < deviceCount; i++)
{
read = 0;
data[i].width = width;
data[i].height = height;
data[i].length = length;
saveData[i].u = (float *)malloc(width*height*length*sizeof(float));
saveData[i].solid = (bool *)malloc(width*height*length*sizeof(bool));
saveData[i].name = (char *)malloc(13*sizeof(char));
saveData[i].width = width;
66
saveData[i].height = height;
saveData[i].length = length;
saveData[i].commRank = commRank;
// Initialize with no solids (0 state)
for (j=0; j<width*height*length; j++)
{
solid[j] = 0;
solid_bound[j] = 0;
}
// Open the input file and set at initial data point
input = fopen("input.bmp","r");
for (y=0; y<height; y++)
{
fseek(input, 0x3E + y*img_width/8 + (i+commRank*deviceCount)*width/8, SEEK_SET);
for (x=0; x < width/8; x++)
{
// Read if current point is solid and save to 'solid'
read=fscanf(input, "%c", &temp) + read;
for (k = 8; k > 0; k--)
if ( !( ( (int)temp & (1 << k-1) ) >> k-1 ) )
solid[(x + y*width/8)*8+(8-k)] = 1;
}
}
if (read != width*height/8) printf("\nerror: verify input.bmp\n\n");
// Close input file
fclose(input);
//Extend length
for (z=0 ; z<length; z++) for (y=0; y<height; y++) for (x=0; x<width; x++)
solid[x + y*width + z*height*width] = solid[x + y*width];
for (j=0; j < width*height*length; j++) saveData[i].solid[j] = solid[j];
// If all boundaries are solid, solid_bound=1;
for (z=0; z < length; z++) for (y=0; y<height; y++)
for (x=1; x<width-1; x++)
{
xn = x-1;
xp = x+1;
// If y is the most top node, the top neighbor will be the most bottom node
yn = (y > 0 ) ? (y-1) : (height-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
yp = (y < (height-1)) ? (y+1) : (0 );
// If y is the most top node, the top neighbor will be the most bottom node
zn = (z > 0 ) ? (z-1) : (length-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
zp = (z < (length-1)) ? (z+1) : (0 );
if
(
solid[ x + y *width + z *width*height ] &&
solid[ xp + y *width + z *width*height ] &&
solid[ xn + y *width + z *width*height ] &&
solid[ x + yp*width + z *width*height ] &&
solid[ x + yn*width + z *width*height ] &&
solid[ x + y *width + zp*width*height ] &&
solid[ x + y *width + zn*width*height ] &&
solid[ xp + yp*width + z *width*height ] &&
solid[ xp + yn*width + z *width*height ] &&
solid[ xp + y *width + zp*width*height ] &&
solid[ xp + y *width + zn*width*height ] &&
solid[ xn + yp*width + z *width*height ] &&
solid[ xn + yn*width + z *width*height ] &&
solid[ xn + y *width + zp*width*height ] &&
solid[ xn + y *width + zn*width*height ] &&
solid[ x + yp*width + zp*width*height ] &&
solid[ x + yp*width + zn*width*height ] &&
solid[ x + yn*width + zp*width*height ] &&
solid[ x + yn*width + zn*width*height ]
)
solid_bound[x+y*width+z*width*height] = 1;
}
// Set current device
CUDA_SAFE_CALL( cudaSetDevice( i ) );
// Allocate all memory from data structure to current device
CUDA_SAFE_CALL( cudaMalloc( &data[i].u, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].solid, sizeof(bool)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].solid_bound, sizeof(bool)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f0, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f1, sizeof(float)*width*height*length ) );
67
CUDA_SAFE_CALL( cudaMalloc( &data[i].f2, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f3, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f4, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f5, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f6, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f7, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f8, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f9, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f10, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f11, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f12, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f13, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f14, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f15, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f16, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f17, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].f18, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f0_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f1_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f2_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f3_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f4_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f5_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f6_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f7_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f8_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f9_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f10_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f11_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f12_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f13_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f14_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f15_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f16_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f17_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL(cudaMalloc( &data[i].f18_new, sizeof(float)*width*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f1 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f7 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f8 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f9 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f10, sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f2 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f11, sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f12, sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f13, sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].border_f14, sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f1 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f7 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f8 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f9 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f10, sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f2 , sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f11, sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f12, sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f13, sizeof(float)*height*length ) );
CUDA_SAFE_CALL( cudaMalloc( &data[i].nb_border_f14, sizeof(float)*height*length ) );
// Create the stream for current device
CUDA_SAFE_CALL( cudaStreamCreate(&data[i].stream) );
// Copy initial data from host to the device
CUDA_SAFE_CALL( cudaMemcpy( data[i].u, u , sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].solid, solid, sizeof(bool)*width*height*length,
cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].solid_bound, solid_bound, sizeof(bool)*width*height*length,
cudaMemcpyDefault ) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f0, f0, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].f1, f1, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].f2, f2, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].f3, f3, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].f4, f4, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].f5, f5, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].f6, f6, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].f7, f7, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
68
CUDA_SAFE_CALL( cudaMemcpy( data[i].f8, f8, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].f9, f9, sizeof(float)*width*height*length, cudaMemcpyDefault )
);
CUDA_SAFE_CALL( cudaMemcpy( data[i].f10, f10, sizeof(float)*width*height*length, cudaMemcpyDefault
) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f11, f11, sizeof(float)*width*height*length, cudaMemcpyDefault
) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f12, f12, sizeof(float)*width*height*length, cudaMemcpyDefault
) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f13, f13, sizeof(float)*width*height*length, cudaMemcpyDefault
) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f14, f14, sizeof(float)*width*height*length, cudaMemcpyDefault
) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f15, f15, sizeof(float)*width*height*length, cudaMemcpyDefault
) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f16, f16, sizeof(float)*width*height*length, cudaMemcpyDefault
) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f17, f17, sizeof(float)*width*height*length, cudaMemcpyDefault
) );
CUDA_SAFE_CALL( cudaMemcpy( data[i].f18, f18, sizeof(float)*width*height*length, cudaMemcpyDefault
) );
}
// Deallocate host memories
free( u );
free( solid );
free( solid_bound );
free( f0 );
free( f1 );
free( f2 );
free( f3 );
free( f4 );
free( f5 );
free( f6 );
free( f7 );
free( f8 );
free( f9 );
free( f10 );
free( f11 );
free( f12 );
free( f13 );
free( f14 );
free( f15 );
free( f16 );
free( f17 );
free( f18 );
}
extern "C"
void initialize_cpu( DataStructCpu *data, SaveStruct *saveData, float ux, float uy,
float rho, float w0, float w1, float w5, int width, int height,
int img_width, int commRank)
{
// Create auxiliary variables
char temp;
int read = 0;
// Counters
int i, j, k;
int xn, x, xp, yn, y, yp;
// Input file
FILE *input;
// Allocate memories
data->u = (float *)malloc(width*height*sizeof(float));
data->solid = (bool *)malloc(width*height*sizeof(bool));
data->solid_bound = (bool *)malloc(width*height*sizeof(bool));
data->f0 = (float *)malloc(width*height*sizeof(float));
data->f1 = (float *)malloc(width*height*sizeof(float));
data->f2 = (float *)malloc(width*height*sizeof(float));
data->f3 = (float *)malloc(width*height*sizeof(float));
data->f4 = (float *)malloc(width*height*sizeof(float));
data->f5 = (float *)malloc(width*height*sizeof(float));
data->f6 = (float *)malloc(width*height*sizeof(float));
data->f7 = (float *)malloc(width*height*sizeof(float));
data->f8 = (float *)malloc(width*height*sizeof(float));
data->f0_new = (float *)malloc(width*height*sizeof(float));
data->f1_new = (float *)malloc(width*height*sizeof(float));
data->f2_new = (float *)malloc(width*height*sizeof(float));
data->f3_new = (float *)malloc(width*height*sizeof(float));
data->f4_new = (float *)malloc(width*height*sizeof(float));
data->f5_new = (float *)malloc(width*height*sizeof(float));
data->f6_new = (float *)malloc(width*height*sizeof(float));
data->f7_new = (float *)malloc(width*height*sizeof(float));
69
data->f8_new = (float *)malloc(width*height*sizeof(float));
data->border_f1 = (float *)malloc(height*sizeof(float));
data->border_f3 = (float *)malloc(height*sizeof(float));
data->border_f5 = (float *)malloc(height*sizeof(float));
data->border_f6 = (float *)malloc(height*sizeof(float));
data->border_f7 = (float *)malloc(height*sizeof(float));
data->border_f8 = (float *)malloc(height*sizeof(float));
saveData->u = (float *)malloc(width*height*sizeof(float));
saveData->solid = (bool *)malloc(width*height*sizeof(bool));
saveData->name = (char *)malloc(13*sizeof(char));
saveData->width = width;
saveData->height = height;
saveData->length = 1;
saveData->commRank = commRank;
// Calculate initial density functions for initial velocity condition
for (i=0; i<width*height; i++)
{
data->f0[i] = w0*rho*( 1. - 1.5*(ux*ux+uy*uy) );
data->f1[i] = w1*rho*( 1. + 3.*(+ux ) + 4.5*(+ux )*(+ux ) - 1.5*(ux*ux+uy*uy) );
data->f2[i] = w1*rho*( 1. + 3.*( +uy) + 4.5*( +uy)*( +uy) - 1.5*(ux*ux+uy*uy) );
data->f3[i] = w1*rho*( 1. + 3.*(-ux ) + 4.5*(-ux )*(-ux ) - 1.5*(ux*ux+uy*uy) );
data->f4[i] = w1*rho*( 1. + 3.*( -uy) + 4.5*( -uy)*( -uy) - 1.5*(ux*ux+uy*uy) );
data->f5[i] = w5*rho*( 1. + 3.*( ux+uy) + 4.5*( ux+uy)*( ux+uy) - 1.5*(ux*ux+uy*uy) );
data->f6[i] = w5*rho*( 1. + 3.*(-ux+uy) + 4.5*(-ux+uy)*(-ux+uy) - 1.5*(ux*ux+uy*uy) );
data->f7[i] = w5*rho*( 1. + 3.*(-ux-uy) + 4.5*(-ux-uy)*(-ux-uy) - 1.5*(ux*ux+uy*uy) );
data->f8[i] = w5*rho*( 1. + 3.*( ux-uy) + 4.5*( ux-uy)*( ux-uy) - 1.5*(ux*ux+uy*uy) );
data->u[i] = sqrtf(ux*ux+uy*uy);
}
// Initialize with no solids (0 state)
for (j=0; j<width*height; j++)
{
data->solid[j] = 0;
data->solid_bound[j] = 0;
}
// Open the input file
input = fopen("input.bmp","r");
for (y=0; y<height; y++)
{
fseek(input, 0x3E + y*img_width/8 + commRank*width/8, SEEK_SET);
for (x=0; x < width/8; x++)
{
// Read if current point is solid and save to 'solid'
read=fscanf(input, "%c", &temp) + read;
for (k = 8; k > 0; k--)
if ( !( ( (int)temp & (1 << k-1) ) >> k-1 ) )
data->solid[(x + y*width/8)*8+(8-k)] = 1;
}
}
if (read != width*height/8) printf("\nerror: verify input.bmp\n\n");
// Close input file
fclose(input);
for (j=0; j < width*height; j++) saveData->solid[j] = data->solid[j];
// If all boundaries are solid, solid_bound=1;
for (y=0; y<height; y++) for (x=1; x<width-1; x++)
{
xn = x-1;
xp = x+1;
// If y is the most top node, the top neighbor will be the most bottom node
yn = (y > 0 ) ? (y-1) : (height-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
yp = (y < (height-1)) ? (y+1) : (0 );
if (data->solid[x+y*width] && data->solid[xp+y*width] && data->solid[x+yp*width] && data-
>solid[xn+y*width] && data->solid[x+yn*width] &&
data->solid[xp+yp*width] && data->solid[xn+yp*width] && data->solid[xn+yn*width] && data-
>solid[xp+yn*width])
data->solid_bound[x+y*width] = 1;
}
data->width = width;
data->height = height;
}
extern "C"
void initialize_3d_cpu( DataStruct3dCpu *data, SaveStruct *saveData, float ux, float uy,
float uz, float rho, float w0, float w1, float w7, int width, int height,
int length, int img_width, int commRank)
{
70
// Create auxiliary variables
char temp;
int read = 0;
// Counters
int i, j, k;
int xn, x, xp, yn, y, yp, zn, z, zp;
// Input file
FILE *input;
data->width = width;
data->height = height;
data->length = length;
// Allocate memories
data->u = (float *)malloc(width*height*length*sizeof(float));
data->solid = (bool *)malloc(width*height*length*sizeof(bool));
data->solid_bound = (bool *)malloc(width*height*length*sizeof(bool));
data->f0 = (float *)malloc(width*height*length*sizeof(float));
data->f1 = (float *)malloc(width*height*length*sizeof(float));
data->f2 = (float *)malloc(width*height*length*sizeof(float));
data->f3 = (float *)malloc(width*height*length*sizeof(float));
data->f4 = (float *)malloc(width*height*length*sizeof(float));
data->f5 = (float *)malloc(width*height*length*sizeof(float));
data->f6 = (float *)malloc(width*height*length*sizeof(float));
data->f7 = (float *)malloc(width*height*length*sizeof(float));
data->f8 = (float *)malloc(width*height*length*sizeof(float));
data->f9 = (float *)malloc(width*height*length*sizeof(float));
data->f10 = (float *)malloc(width*height*length*sizeof(float));
data->f11 = (float *)malloc(width*height*length*sizeof(float));
data->f12 = (float *)malloc(width*height*length*sizeof(float));
data->f13 = (float *)malloc(width*height*length*sizeof(float));
data->f14 = (float *)malloc(width*height*length*sizeof(float));
data->f15 = (float *)malloc(width*height*length*sizeof(float));
data->f16 = (float *)malloc(width*height*length*sizeof(float));
data->f17 = (float *)malloc(width*height*length*sizeof(float));
data->f18 = (float *)malloc(width*height*length*sizeof(float));
data->f0_new = (float *)malloc(width*height*length*sizeof(float));
data->f1_new = (float *)malloc(width*height*length*sizeof(float));
data->f2_new = (float *)malloc(width*height*length*sizeof(float));
data->f3_new = (float *)malloc(width*height*length*sizeof(float));
data->f4_new = (float *)malloc(width*height*length*sizeof(float));
data->f5_new = (float *)malloc(width*height*length*sizeof(float));
data->f6_new = (float *)malloc(width*height*length*sizeof(float));
data->f7_new = (float *)malloc(width*height*length*sizeof(float));
data->f8_new = (float *)malloc(width*height*length*sizeof(float));
data->f9_new = (float *)malloc(width*height*length*sizeof(float));
data->f10_new = (float *)malloc(width*height*length*sizeof(float));
data->f11_new = (float *)malloc(width*height*length*sizeof(float));
data->f12_new = (float *)malloc(width*height*length*sizeof(float));
data->f13_new = (float *)malloc(width*height*length*sizeof(float));
data->f14_new = (float *)malloc(width*height*length*sizeof(float));
data->f15_new = (float *)malloc(width*height*length*sizeof(float));
data->f16_new = (float *)malloc(width*height*length*sizeof(float));
data->f17_new = (float *)malloc(width*height*length*sizeof(float));
data->f18_new = (float *)malloc(width*height*length*sizeof(float));
data->border_f1 = (float *)malloc(height*length*sizeof(float));
data->border_f7 = (float *)malloc(height*length*sizeof(float));
data->border_f8 = (float *)malloc(height*length*sizeof(float));
data->border_f9 = (float *)malloc(height*length*sizeof(float));
data->border_f10 = (float *)malloc(height*length*sizeof(float));
data->border_f2 = (float *)malloc(height*length*sizeof(float));
data->border_f11 = (float *)malloc(height*length*sizeof(float));
data->border_f12 = (float *)malloc(height*length*sizeof(float));
data->border_f13 = (float *)malloc(height*length*sizeof(float));
data->border_f14 = (float *)malloc(height*length*sizeof(float));
saveData->u = (float *)malloc(width*height*length*sizeof(float));
saveData->solid = (bool *)malloc(width*height*length*sizeof(bool));
saveData->name = (char *)malloc(13*sizeof(char));
saveData->width = width;
saveData->height = height;
saveData->length = length;
saveData->commRank = commRank;
for (i=0; i<width*height*length; i++)
{
data->f0[i] = w0*rho*( 1. - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f1[i] = w1*rho*( 1. + 3.*(+ux ) + 4.5*(+ux )*(+ux ) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f2[i] = w1*rho*( 1. + 3.*(-ux ) + 4.5*(-ux )*(-ux ) - 1.5*(ux*ux+uy*uy+uz*uz)
);
71
data->f3[i] = w1*rho*( 1. + 3.*( +uy ) + 4.5*( +uy )*( +uy ) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f4[i] = w1*rho*( 1. + 3.*( -uy ) + 4.5*( -uy )*( -uy ) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f5[i] = w1*rho*( 1. + 3.*( +uz) + 4.5*( +uz)*( +uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f6[i] = w1*rho*( 1. + 3.*( -uz) + 4.5*( -uz)*( -uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f7[i] = w7*rho*( 1. + 3.*(+ux+uy ) + 4.5*(+ux+uy )*(+ux+uy ) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f8[i] = w7*rho*( 1. + 3.*(+ux-uy ) + 4.5*(+ux-uy )*(+ux-uy ) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f9[i] = w7*rho*( 1. + 3.*(+ux +uz) + 4.5*(+ux +uz)*(+ux +uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f10[i]= w7*rho*( 1. + 3.*(+ux -uz) + 4.5*(+ux -uz)*(+ux -uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f11[i]= w7*rho*( 1. + 3.*(-ux+uy ) + 4.5*(-ux+uy )*(-ux+uy ) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f12[i]= w7*rho*( 1. + 3.*(-ux-uy ) + 4.5*(-ux-uy )*(-ux-uy ) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f13[i]= w7*rho*( 1. + 3.*(-ux +uz) + 4.5*(-ux +uz)*(-ux +uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f14[i]= w7*rho*( 1. + 3.*(-ux -uz) + 4.5*(-ux -uz)*(-ux -uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f15[i]= w7*rho*( 1. + 3.*( +uy+uz) + 4.5*( +uy+uz)*( +uy+uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f16[i]= w7*rho*( 1. + 3.*( +uy-uz) + 4.5*( +uy-uz)*( +uy-uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f17[i]= w7*rho*( 1. + 3.*( -uy+uz) + 4.5*( -uy+uz)*( -uy+uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->f18[i]= w7*rho*( 1. + 3.*( -uy-uz) + 4.5*( -uy-uz)*( -uy-uz) - 1.5*(ux*ux+uy*uy+uz*uz)
);
data->u[i] = sqrtf(ux*ux+uy*uy+uz*uz);
}
// Initialize with no solids (0 state)
for (j=0; j<width*height*length; j++)
{
data->solid[j] = 0;
data->solid_bound[j] = 0;
}
// Open the input file
input = fopen("input.bmp","r");
for (y=0; y<height; y++)
{
fseek(input, 0x3E + y*img_width/8 + commRank*width/8, SEEK_SET);
for (x=0; x < width/8; x++)
{
// Read if current point is solid and save to 'solid'
read=fscanf(input, "%c", &temp) + read;
for (k = 8; k > 0; k--)
if ( !( ( (int)temp & (1 << k-1) ) >> k-1 ) )
data->solid[(x + y*width/8)*8+(8-k)] = 1;
}
}
if (read != width*height/8) printf("\nerror: verify input.bmp\n\n");
// Close input file
fclose(input);
//Extend length
for (z=0 ; z<length; z++) for (y=0; y<height; y++) for (x=0; x<width; x++)
data->solid[x + y*width + z*height*width] = data->solid[x + y*width];
for (j=0; j < width*height*length; j++) saveData->solid[j] = data->solid[j];
// If all boundaries are solid, solid_bound=1;
for (z=0; z < length; z++) for (y=0; y<height; y++) for (x=1; x<width-1; x++)
{
xn = x-1;
xp = x+1;
// If y is the most top node, the top neighbor will be the most bottom node
yn = (y > 0 ) ? (y-1) : (height-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
yp = (y < (height-1)) ? (y+1) : (0 );
// If y is the most top node, the top neighbor will be the most bottom node
zn = (z > 0 ) ? (z-1) : (length-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
zp = (z < (length-1)) ? (z+1) : (0 );
if
(
data->solid[ x + y *width + z *width*height ] &&
data->solid[ xp + y *width + z *width*height ] &&
72
data->solid[ xn + y *width + z *width*height ] &&
data->solid[ x + yp*width + z *width*height ] &&
data->solid[ x + yn*width + z *width*height ] &&
data->solid[ x + y *width + zp*width*height ] &&
data->solid[ x + y *width + zn*width*height ] &&
data->solid[ xp + yp*width + z *width*height ] &&
data->solid[ xp + yn*width + z *width*height ] &&
data->solid[ xp + y *width + zp*width*height ] &&
data->solid[ xp + y *width + zn*width*height ] &&
data->solid[ xn + yp*width + z *width*height ] &&
data->solid[ xn + yn*width + z *width*height ] &&
data->solid[ xn + y *width + zp*width*height ] &&
data->solid[ xn + y *width + zn*width*height ] &&
data->solid[ x + yp*width + zp*width*height ] &&
data->solid[ x + yp*width + zn*width*height ] &&
data->solid[ x + yn*width + zp*width*height ] &&
data->solid[ x + yn*width + zn*width*height ]
)
data->solid_bound[x+y*width+z*width*height] = 1;
}
}
73
streaming.h:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 28.11.2012
* UNICAMP - Universidade Estadual de Campinas
*
* This header file defines the streaming function. This functions passes the actual density
* functions to the nearest lattice node in 'fx_new' according to its direction.
**************************************************************************************************/
#ifndef STREAMING_H
#define STREAMING_H
extern "C"
void streaming( DataStruct *data, dim3 grid, dim3 block );
extern "C"
void streaming_3d( DataStruct3d *data, dim3 grid, dim3 block );
extern "C"
void streaming_cpu( DataStructCpu *data );
extern "C"
void streaming_3d_cpu( DataStruct3dCpu *data );
#endif
streaming.cu:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* Streaming C and CUDA kernel functions
**************************************************************************************************/
#include <cutil.h>
#include "latibol.h"
__global__ void streaming_kernel ( DataStruct data )
{
// Create variables that defines neighbors and current coordinates
int xn, x, xp, yn, y, yp;
int offset[9];
int dim;
x = threadIdx.x + blockIdx.x * blockDim.x;
y = threadIdx.y + blockIdx.y * blockDim.y;
dim = blockDim.x * gridDim.x;
offset[0] = (x ) + (y ) * dim;
if ( !data.solid_bound[offset[0]] )
{
// Apply periodic boundary condition
// If x is the most left node, the left neighbor will be the most right node
xn = (x > 0 ) ? (x-1) : (data.width-1 );
// If x is the most right node, the right neighbor will be the most left node
xp = (x < (data.width-1) ) ? (x+1) : (0 );
// If y is the most top node, the top neighbor will be the most bottom node
yn = (y > 0 ) ? (y-1) : (data.height-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
yp = (y < (data.height-1)) ? (y+1) : (0 );
// Calculate offset for fx_new
offset[1] = (xp) + (y ) * dim;
offset[2] = (x ) + (yp) * dim;
offset[3] = (xn) + (y ) * dim;
offset[4] = (x ) + (yn) * dim;
offset[5] = (xp) + (yp) * dim;
offset[6] = (xn) + (yp) * dim;
offset[7] = (xn) + (yn) * dim;
offset[8] = (xp) + (yn) * dim;
74
// Apply streaming process moving current densities functions to next position
data.f0_new[offset[0]] = data.f0[offset[0]];
data.f1_new[offset[1]] = data.f1[offset[0]];
data.f2_new[offset[2]] = data.f2[offset[0]];
data.f3_new[offset[3]] = data.f3[offset[0]];
data.f4_new[offset[4]] = data.f4[offset[0]];
data.f5_new[offset[5]] = data.f5[offset[0]];
data.f6_new[offset[6]] = data.f6[offset[0]];
data.f7_new[offset[7]] = data.f7[offset[0]];
data.f8_new[offset[8]] = data.f8[offset[0]];
}
}
// C streaming function that calls CUDA kernel
extern "C"
void streaming( DataStruct *data, dim3 grid, dim3 block )
{
streaming_kernel<<<grid, block, 0, data->stream>>> ( *data );
CUT_CHECK_ERROR("streaming failed");
}
__global__ void streaming_3d_kernel ( DataStruct3d data )
{
// Create variables that defines neighbors and current coordinates
int xn, x, xp, yn, y, yp, zn, z, zp;
int offset[19];
x = threadIdx.x + blockIdx.x * blockDim.x;
y = threadIdx.y + blockIdx.y * blockDim.y;
z = threadIdx.z + blockIdx.z * blockDim.z;
int x_dim = blockDim.x * gridDim.x;
int xy_dim = x_dim*blockDim.y * gridDim.y;
offset[0] = (x ) + (y )*x_dim + (z )*xy_dim;
if (!data.solid_bound[offset[0]] )
{
// Apply periodic boundary condition
// If x is the most left node, the left neighbor will be the most right node
xn = (x > 0 ) ? (x-1) : (data.width-1 );
// If x is the most right node, the right neighbor will be the most left node
xp = (x < (data.width-1) ) ? (x+1) : (0 );
// If y is the most top node, the top neighbor will be the most bottom node
yn = (y > 0 ) ? (y-1) : (data.height-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
yp = (y < (data.height-1)) ? (y+1) : (0 );
// If y is the most top node, the top neighbor will be the most bottom node
zn = (z > 0 ) ? (z-1) : (data.length-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
zp = (z < (data.length-1)) ? (z+1) : (0 );
// Calculate offset for fx_new
offset[1] = (xp) + (y )*x_dim + (z )*xy_dim;
offset[2] = (xn) + (y )*x_dim + (z )*xy_dim;
offset[3] = (x ) + (yp)*x_dim + (z )*xy_dim;
offset[4] = (x ) + (yn)*x_dim + (z )*xy_dim;
offset[5] = (x ) + (y )*x_dim + (zp)*xy_dim;
offset[6] = (x ) + (y )*x_dim + (zn)*xy_dim;
offset[7] = (xp) + (yp)*x_dim + (z )*xy_dim;
offset[8] = (xp) + (yn)*x_dim + (z )*xy_dim;
offset[9] = (xp) + (y )*x_dim + (zp)*xy_dim;
offset[10]= (xp) + (y )*x_dim + (zn)*xy_dim;
offset[11]= (xn) + (yp)*x_dim + (z )*xy_dim;
offset[12]= (xn) + (yn)*x_dim + (z )*xy_dim;
offset[13]= (xn) + (y )*x_dim + (zp)*xy_dim;
offset[14]= (xn) + (y )*x_dim + (zn)*xy_dim;
offset[15]= (x ) + (yp)*x_dim + (zp)*xy_dim;
offset[16]= (x ) + (yp)*x_dim + (zn)*xy_dim;
offset[17]= (x ) + (yn)*x_dim + (zp)*xy_dim;
offset[18]= (x ) + (yn)*x_dim + (zn)*xy_dim;
// Apply streaming process moving current densities functions to next position
data.f0_new [offset[0]] = data.f0 [offset[0]];
data.f1_new [offset[1]] = data.f1 [offset[0]];
data.f2_new [offset[2]] = data.f2 [offset[0]];
data.f3_new [offset[3]] = data.f3 [offset[0]];
data.f4_new [offset[4]] = data.f4 [offset[0]];
data.f5_new [offset[5]] = data.f5 [offset[0]];
data.f6_new [offset[6]] = data.f6 [offset[0]];
data.f7_new [offset[7]] = data.f7 [offset[0]];
data.f8_new [offset[8]] = data.f8 [offset[0]];
data.f9_new [offset[9]] = data.f9 [offset[0]];
data.f10_new[offset[10]] = data.f10[offset[0]];
data.f11_new[offset[11]] = data.f11[offset[0]];
data.f12_new[offset[12]] = data.f12[offset[0]];
75
data.f13_new[offset[13]] = data.f13[offset[0]];
data.f14_new[offset[14]] = data.f14[offset[0]];
data.f15_new[offset[15]] = data.f15[offset[0]];
data.f16_new[offset[16]] = data.f16[offset[0]];
data.f17_new[offset[17]] = data.f17[offset[0]];
data.f18_new[offset[18]] = data.f18[offset[0]];
}
}
extern "C"
void streaming_3d( DataStruct3d *data, dim3 grid, dim3 block )
{
streaming_3d_kernel<<<grid, block, 0, data->stream>>> ( *data );
CUT_CHECK_ERROR("streaming failed");
}
extern "C"
void streaming_cpu( DataStructCpu *data )
{
// Create variables that defines neighbors and current coordinates
int xn, x, xp, yn, y, yp;
int offset[9];
int width = data->width;
for (y = 0; y < data->height; y++) for (x = 0; x < width; x++)
{
offset[0] = (x ) + (y ) * width;
if ( !data->solid_bound[offset[0]] )
{
// Apply periodic boundary condition
// If x is the most left node, the left neighbor will be the most right node
xn = (x > 0 ) ? (x-1) : (width-1 );
// If x is the most right node, the right neighbor will be the most left node
xp = (x < (width-1) ) ? (x+1) : (0 );
// If y is the most top node, the top neighbor will be the most bottom node
yn = (y > 0 ) ? (y-1) : (data->height-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
yp = (y < (data->height-1)) ? (y+1) : (0 );
// Calculate offset for fx_new
offset[1] = (xp) + (y ) * width;
offset[2] = (x ) + (yp) * width;
offset[3] = (xn) + (y ) * width;
offset[4] = (x ) + (yn) * width;
offset[5] = (xp) + (yp) * width;
offset[6] = (xn) + (yp) * width;
offset[7] = (xn) + (yn) * width;
offset[8] = (xp) + (yn) * width;
// Apply streaming process moving current densities functions to next position
data->f0_new[offset[0]] = data->f0[offset[0]];
data->f1_new[offset[1]] = data->f1[offset[0]];
data->f2_new[offset[2]] = data->f2[offset[0]];
data->f3_new[offset[3]] = data->f3[offset[0]];
data->f4_new[offset[4]] = data->f4[offset[0]];
data->f5_new[offset[5]] = data->f5[offset[0]];
data->f6_new[offset[6]] = data->f6[offset[0]];
data->f7_new[offset[7]] = data->f7[offset[0]];
data->f8_new[offset[8]] = data->f8[offset[0]];
}
}
}
extern "C"
void streaming_3d_cpu( DataStruct3dCpu *data )
{
// Create variables that defines neighbors and current coordinates
int xn, x, xp, yn, y, yp, zn, z, zp;
int offset[19];
int x_dim = data->width;
int xy_dim = x_dim*data->height;
for (z=0; z < data->length; z++) for (y = 0; y < data->height; y++) for (x = 0; x < data->width; x++)
{
offset[0] = (x ) + (y )*x_dim + (z )*xy_dim;
if ( !data->solid_bound[offset[0]] )
{
// Apply periodic boundary condition
// If x is the most left node, the left neighbor will be the most right node
xn = (x > 0 ) ? (x-1) : (data->width-1 );
// If x is the most right node, the right neighbor will be the most left node
xp = (x < (data->width-1) ) ? (x+1) : (0 );
// If y is the most top node, the top neighbor will be the most bottom node
yn = (y > 0 ) ? (y-1) : (data->height-1);
76
// If y is the most bottom node, the bottom neighbor will be the most top node
yp = (y < (data->height-1)) ? (y+1) : (0 );
// If y is the most top node, the top neighbor will be the most bottom node
zn = (z > 0 ) ? (z-1) : (data->length-1);
// If y is the most bottom node, the bottom neighbor will be the most top node
zp = (z < (data->length-1)) ? (z+1) : (0 );
// Calculate offset for fx_new
offset[1] = (xp) + (y )*x_dim + (z )*xy_dim;
offset[2] = (xn) + (y )*x_dim + (z )*xy_dim;
offset[3] = (x ) + (yp)*x_dim + (z )*xy_dim;
offset[4] = (x ) + (yn)*x_dim + (z )*xy_dim;
offset[5] = (x ) + (y )*x_dim + (zp)*xy_dim;
offset[6] = (x ) + (y )*x_dim + (zn)*xy_dim;
offset[7] = (xp) + (yp)*x_dim + (z )*xy_dim;
offset[8] = (xp) + (yn)*x_dim + (z )*xy_dim;
offset[9] = (xp) + (y )*x_dim + (zp)*xy_dim;
offset[10]= (xp) + (y )*x_dim + (zn)*xy_dim;
offset[11]= (xn) + (yp)*x_dim + (z )*xy_dim;
offset[12]= (xn) + (yn)*x_dim + (z )*xy_dim;
offset[13]= (xn) + (y )*x_dim + (zp)*xy_dim;
offset[14]= (xn) + (y )*x_dim + (zn)*xy_dim;
offset[15]= (x ) + (yp)*x_dim + (zp)*xy_dim;
offset[16]= (x ) + (yp)*x_dim + (zn)*xy_dim;
offset[17]= (x ) + (yn)*x_dim + (zp)*xy_dim;
offset[18]= (x ) + (yn)*x_dim + (zn)*xy_dim;
// Apply streaming process moving current densities functions to next position
data->f0_new [offset[0]] = data->f0 [offset[0]];
data->f1_new [offset[1]] = data->f1 [offset[0]];
data->f2_new [offset[2]] = data->f2 [offset[0]];
data->f3_new [offset[3]] = data->f3 [offset[0]];
data->f4_new [offset[4]] = data->f4 [offset[0]];
data->f5_new [offset[5]] = data->f5 [offset[0]];
data->f6_new [offset[6]] = data->f6 [offset[0]];
data->f7_new [offset[7]] = data->f7 [offset[0]];
data->f8_new [offset[8]] = data->f8 [offset[0]];
data->f9_new [offset[9]] = data->f9 [offset[0]];
data->f10_new[offset[10]] = data->f10[offset[0]];
data->f11_new[offset[11]] = data->f11[offset[0]];
data->f12_new[offset[12]] = data->f12[offset[0]];
data->f13_new[offset[13]] = data->f13[offset[0]];
data->f14_new[offset[14]] = data->f14[offset[0]];
data->f15_new[offset[15]] = data->f15[offset[0]];
data->f16_new[offset[16]] = data->f16[offset[0]];
data->f17_new[offset[17]] = data->f17[offset[0]];
data->f18_new[offset[18]] = data->f18[offset[0]];
}
}
}
77
get_border.h:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* This header file defines the get_border function. This functions passes the most right density
* functions f1, f5, and f8 from fx_new to border_fx and the most left density functions
* f3, f6, and f7 from fx_new to border_fx
**************************************************************************************************/
#ifndef GET_BORDER_H
#define GET_BORDER_H
extern "C"
void get_border( DataStruct *data, dim3 grid, dim3 block );
extern "C"
void get_border_3d( DataStruct3d *data, dim3 grid, dim3 block );
extern "C"
void get_border_cpu( DataStructCpu *data );
extern "C"
void get_border_3d_cpu( DataStruct3dCpu *data );
#endif
get_border.cu:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* Get_border C and CUDA kernel functions
**************************************************************************************************/
#include <cutil.h>
#include <cuda_runtime_api.h>
#include "latibol.h"
__global__ void get_border_kernel ( DataStruct data )
{
int x = threadIdx.x + blockIdx.x * blockDim.x;
// if x is the most right node copies fx_new to border_fx
if (x == (data.width-1))
{
int y = threadIdx.y + blockIdx.y * blockDim.y;
int offset = x + y * blockDim.x * gridDim.x;
data.border_f1[y] = data.f1_new[offset];
data.border_f5[y] = data.f5_new[offset];
data.border_f8[y] = data.f8_new[offset];
}
// if x is the most left node copies fx_new to border_fx
if (x == 0)
{
int y = threadIdx.y + blockIdx.y * blockDim.y;
int offset = x + y * blockDim.x * gridDim.x;
data.border_f3[y] = data.f3_new[offset];
data.border_f6[y] = data.f6_new[offset];
data.border_f7[y] = data.f7_new[offset];
}
}
// C get_border function that calls CUDA kernel
extern "C"
void get_border( DataStruct *data, dim3 grid, dim3 block )
{
get_border_kernel<<<grid, block, 0, data->stream>>> ( *data );
}
__global__ void get_border_3d_kernel ( DataStruct3d data )
{
78
int x = threadIdx.x + blockIdx.x * blockDim.x;
// if x is the most right node copies fx_new to border_fx
if (x == (data.width-1))
{
int y = threadIdx.y + blockIdx.y*blockDim.y;
int z = threadIdx.z + blockIdx.z*blockDim.z;
int offset = x + y * blockDim.x*gridDim.x + z * blockDim.x*gridDim.x*blockDim.y*gridDim.y ;
data.border_f1 [y+data.height*z] = data.f1_new [offset];
data.border_f7 [y+data.height*z] = data.f7_new [offset];
data.border_f8 [y+data.height*z] = data.f8_new [offset];
data.border_f9 [y+data.height*z] = data.f9_new [offset];
data.border_f10[y+data.height*z] = data.f10_new[offset];
}
// if x is the most left node copies fx_new to border_fx
if (x == 0)
{
int y = threadIdx.y + blockIdx.y*blockDim.y;
int z = threadIdx.z + blockIdx.z*blockDim.z;
int offset = x + y * blockDim.x*gridDim.x + z * blockDim.x*gridDim.x*blockDim.y*gridDim.y ;
data.border_f2 [y+data.height*z] = data.f2_new [offset];
data.border_f11[y+data.height*z] = data.f11_new[offset];
data.border_f12[y+data.height*z] = data.f12_new[offset];
data.border_f13[y+data.height*z] = data.f13_new[offset];
data.border_f14[y+data.height*z] = data.f14_new[offset];
}
}
extern "C"
void get_border_3d( DataStruct3d *data, dim3 grid, dim3 block )
{
get_border_3d_kernel<<<grid, block, 0, data->stream>>> ( *data );
}
extern "C"
void get_border_cpu( DataStructCpu *data )
{
int x, y, offset;
for (x = 0; x < data->width; x++)
{
// if x is the most right node copies fx_new to border_fx
if (x == (data->width-1)) for (y = 0; y < data->height; y++)
{
offset = x + y*data->width;
data->border_f1[y] = data->f1_new[offset];
data->border_f5[y] = data->f5_new[offset];
data->border_f8[y] = data->f8_new[offset];
}
// if x is the most left node copies fx_new to border_fx
if (x == 0) for (y = 0; y < data->height; y++)
{
offset = x + y*data->width;
data->border_f3[y] = data->f3_new[offset];
data->border_f6[y] = data->f6_new[offset];
data->border_f7[y] = data->f7_new[offset];
}
}
}
extern "C"
void get_border_3d_cpu( DataStruct3dCpu *data )
{
int x, y, z, offset, border_offset;
int width = data->width;
int height = data->height;
int length = data-> length;
for (x = 0; x < width; x++)
{
// if x is the most right node copies fx_new to border_fx
if (x == (width-1)) for (z=0; z < length; z++) for (y = 0; y < height; y++)
{
offset = x + y*width + z*width*height;
border_offset = y + z*height;
data->border_f1 [border_offset] = data->f1_new [offset];
data->border_f7 [border_offset] = data->f7_new [offset];
data->border_f8 [border_offset] = data->f8_new [offset];
data->border_f9 [border_offset] = data->f9_new [offset];
data->border_f10[border_offset] = data->f10_new[offset];
}
// if x is the most left node copies fx_new to border_fx
79
if (x == 0) for (z=0; z < length; z++) for (y = 0; y < height; y++)
{
offset = x + y*width + z*width*height;
border_offset = y + z*height;
data->border_f2 [border_offset] = data->f2_new [offset];
data->border_f11[border_offset] = data->f11_new[offset];
data->border_f12[border_offset] = data->f12_new[offset];
data->border_f13[border_offset] = data->f13_new[offset];
data->border_f14[border_offset] = data->f14_new[offset];
}
}
}
80
copy_border.h:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* This header file defines the copy_border function. This functions copies nb_border_fx to
* border_fx from any device to any device using UVA, so its necessary compute capability 2.0+.
* To use this function its necessary to pass the current device nb_border_fx, 'left' device
* border_fa and 'right' device border_fa.
**************************************************************************************************/
#ifndef COPY_BORDER_H
#define COPY_BORDER_H
extern "C"
void copy_border_devices( float *nb_border_f1, float *nb_border_f5, float *nb_border_f8,
float *nb_border_f3, float *nb_border_f6, float *nb_border_f7,
float *border_f1, float *border_f5, float *border_f8,
float *border_f3, float *border_f6, float *border_f7,
int height, cudaStream_t &s );
void copy_border_3d_devices( float *nb_border_f1, float *nb_border_f7, float *nb_border_f8,
float *nb_border_f9, float *nb_border_f10,
float *nb_border_f2, float *nb_border_f11, float *nb_border_f12,
float *nb_border_f13, float *nb_border_f14,
float *border_f1, float *border_f7, float *border_f8,
float *border_f9, float *border_f10,
float *border_f2, float *border_f11, float *border_f12,
float *border_f13, float *border_f14,
int height, int length, cudaStream_t &s );
void copy_border_nodes ( float *border_ctr_buf, float *border_top_buf, float *border_btm_buf,
float *nb_border_ctr, float *nb_border_top, float *nb_border_btm,
int height, int dest, int source, MPI_Status Stat, cudaStream_t &s );
void copy_border_3d_nodes ( float *border_ctr_buf, float *border_lft_buf, float *border_rgt_buf,
float *border_top_buf, float *border_btm_buf,
float *nb_border_ctr, float *nb_border_lft, float *nb_border_rgt,
float *nb_border_top, float *nb_border_btm,
int height, int length, int dest, int source, MPI_Status Stat, cudaStream_t &s
);
void copy_border_nodes_cpu ( float *border_ctr_buf, float *border_top_buf, float *border_btm_buf,
int height, int dest, int source, MPI_Status Stat );
void copy_border_3d_nodes_cpu ( float *border_ctr_buf, float *border_lft_buf, float *border_rgt_buf,
float *border_top_buf, float *border_btm_buf,
int height, int length, int dest, int source, MPI_Status Stat );
#endif
copy_border.cu:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* Copy_border function
**************************************************************************************************/
#include <cutil.h>
#include <cuda_runtime_api.h>
#include <mpi.h>
extern "C"
void copy_border_devices( float *nb_border_f1, float *nb_border_f5, float *nb_border_f8,
float *nb_border_f3, float *nb_border_f6, float *nb_border_f7,
float *border_f1, float *border_f5, float *border_f8,
float *border_f3, float *border_f6, float *border_f7,
int height, cudaStream_t &s )
{
// Copies border_fx to nb_border_fx using UVA
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f1, border_f1, sizeof(float)*height,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f5, border_f5, sizeof(float)*height,
cudaMemcpyDefault, s));
81
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f8, border_f8, sizeof(float)*height,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f3, border_f3, sizeof(float)*height,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f6, border_f6, sizeof(float)*height,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f7, border_f7, sizeof(float)*height,
cudaMemcpyDefault, s));
}
void copy_border_3d_devices( float *nb_border_f1, float *nb_border_f7, float *nb_border_f8,
float *nb_border_f9, float *nb_border_f10,
float *nb_border_f2, float *nb_border_f11, float *nb_border_f12,
float *nb_border_f13, float *nb_border_f14,
float *border_f1, float *border_f7, float *border_f8,
float *border_f9, float *border_f10,
float *border_f2, float *border_f11, float *border_f12,
float *border_f13, float *border_f14,
int height, int length, cudaStream_t &s )
{
// Copies border_fx to nb_border_fx using UVA
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f1, border_f1, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f7, border_f7, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f8, border_f8, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f9, border_f9, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f10, border_f10, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f2, border_f2, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f11, border_f11, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f12, border_f12, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f13, border_f13, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_f14, border_f14, sizeof(float)*height*length,
cudaMemcpyDefault, s));
}
void copy_border_nodes ( float *border_ctr_buf, float *border_top_buf, float *border_btm_buf,
float *nb_border_ctr, float *nb_border_top, float *nb_border_btm,
int height, int dest, int source, MPI_Status Stat, cudaStream_t &s )
{
CUDA_SAFE_CALL(cudaMemcpyAsync(border_ctr_buf, nb_border_ctr, sizeof(float)*height,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(border_top_buf, nb_border_top, sizeof(float)*height,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(border_btm_buf, nb_border_btm, sizeof(float)*height,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL( cudaDeviceSynchronize() );
MPI_Sendrecv_replace(border_ctr_buf, height, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD, &Stat);
MPI_Sendrecv_replace(border_top_buf, height, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD, &Stat);
MPI_Sendrecv_replace(border_btm_buf, height, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD, &Stat);
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_ctr, border_ctr_buf, sizeof(float)*height,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_top, border_top_buf, sizeof(float)*height,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_btm, border_btm_buf, sizeof(float)*height,
cudaMemcpyDefault, s));
}
void copy_border_3d_nodes ( float *border_ctr_buf, float *border_lft_buf, float *border_rgt_buf,
float *border_top_buf, float *border_btm_buf,
float *nb_border_ctr, float *nb_border_lft, float *nb_border_rgt,
float *nb_border_top, float *nb_border_btm,
int height, int length, int dest, int source, MPI_Status Stat, cudaStream_t &s
)
{
CUDA_SAFE_CALL(cudaMemcpyAsync(border_ctr_buf, nb_border_ctr, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(border_lft_buf, nb_border_lft, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(border_rgt_buf, nb_border_rgt, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(border_top_buf, nb_border_top, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(border_btm_buf, nb_border_btm, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL( cudaDeviceSynchronize() );
82
MPI_Sendrecv_replace(border_ctr_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
MPI_Sendrecv_replace(border_lft_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
MPI_Sendrecv_replace(border_rgt_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
MPI_Sendrecv_replace(border_top_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
MPI_Sendrecv_replace(border_btm_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_ctr, border_ctr_buf, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_lft, border_lft_buf, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_rgt, border_rgt_buf, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_top, border_top_buf, sizeof(float)*height*length,
cudaMemcpyDefault, s));
CUDA_SAFE_CALL(cudaMemcpyAsync(nb_border_btm, border_btm_buf, sizeof(float)*height*length,
cudaMemcpyDefault, s));
}
void copy_border_nodes_cpu ( float *border_ctr_buf, float *border_top_buf, float *border_btm_buf,
int height, int dest, int source, MPI_Status Stat )
{
MPI_Sendrecv_replace(border_ctr_buf, height, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD, &Stat);
MPI_Sendrecv_replace(border_top_buf, height, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD, &Stat);
MPI_Sendrecv_replace(border_btm_buf, height, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD, &Stat);
}
void copy_border_3d_nodes_cpu ( float *border_ctr_buf, float *border_lft_buf, float *border_rgt_buf,
float *border_top_buf, float *border_btm_buf,
int height, int length, int dest, int source, MPI_Status Stat )
{
MPI_Sendrecv_replace(border_ctr_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
MPI_Sendrecv_replace(border_lft_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
MPI_Sendrecv_replace(border_rgt_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
MPI_Sendrecv_replace(border_top_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
MPI_Sendrecv_replace(border_btm_buf, height*length, MPI_FLOAT, dest, 1, source, 1, MPI_COMM_WORLD,
&Stat);
}
83
apply_border.h:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* This header file defines the apply_border function. This functions apply neighbor borders to
* the fx_new. If the lattice node is the most right it copies f1, f5 and f8, if it is the most
* left node it copies f3, f6, f7.
**************************************************************************************************/
#ifndef APPLY_BORDER_H
#define APPLY_BORDER_H
extern "C"
void apply_border ( DataStruct *data, dim3 grid, dim3 block );
extern "C"
void apply_border_3d ( DataStruct3d *data, dim3 grid, dim3 block );
extern "C"
void apply_border_cpu ( DataStructCpu *data );
extern "C"
void apply_border_3d_cpu ( DataStruct3dCpu *data );
#endif
apply_border.cu:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* Apply_border C and CUDA kernel functions
**************************************************************************************************/
#include <cutil.h>
#include "latibol.h"
__global__ void apply_border_kernel ( DataStruct data )
{
int x = threadIdx.x + blockIdx.x * blockDim.x;
// If the lattice node is the most right it copies f1, f5 and f8 neighbor borders
if (x == 0)
{
int y = threadIdx.y + blockIdx.y * blockDim.y;
int offset = x + y * blockDim.x * gridDim.x;
data.f1_new[offset] = data.nb_border_f1[y];
data.f5_new[offset] = data.nb_border_f5[y];
data.f8_new[offset] = data.nb_border_f8[y];
}
// If the lattice node is the most left it copies f3, f6 and f7 neighbor borders
if (x == (data.width-1))
{
int y = threadIdx.y + blockIdx.y * blockDim.y;
int offset = x + y * blockDim.x * gridDim.x;
data.f3_new[offset] = data.nb_border_f3[y];
data.f6_new[offset] = data.nb_border_f6[y];
data.f7_new[offset] = data.nb_border_f7[y];
}
}
// C apply_border function that calls CUDA kernel
extern "C"
void apply_border ( DataStruct *data, dim3 grid, dim3 block )
{
apply_border_kernel<<<grid, block, 0, data->stream>>> ( *data );
CUT_CHECK_ERROR("apply border failed");
}
__global__ void apply_border_3d_kernel ( DataStruct3d data )
{
int x = threadIdx.x + blockIdx.x * blockDim.x;
84
// If the lattice node is the most right it copies f1, f5 and f8 neighbor borders
if (x == 0)
{
int y = threadIdx.y + blockIdx.y*blockDim.y;
int z = threadIdx.z + blockIdx.z*blockDim.z;
int offset = x + y * blockDim.x*gridDim.x + z * blockDim.x*gridDim.x*blockDim.y*gridDim.y;
data.f1_new [offset] = data.nb_border_f1 [y+data.height*z];
data.f7_new [offset] = data.nb_border_f7 [y+data.height*z];
data.f8_new [offset] = data.nb_border_f8 [y+data.height*z];
data.f9_new [offset] = data.nb_border_f9 [y+data.height*z];
data.f10_new[offset] = data.nb_border_f10[y+data.height*z];
}
// If the lattice node is the most left it copies f3, f6 and f7 neighbor borders
if (x == (data.width-1))
{
int y = threadIdx.y + blockIdx.y*blockDim.y;
int z = threadIdx.z + blockIdx.z*blockDim.z;
int offset = x + y * blockDim.x*gridDim.x + z * blockDim.x*gridDim.x*blockDim.y*gridDim.y;
data.f2_new [offset] = data.nb_border_f2 [y+data.height*z];
data.f11_new[offset] = data.nb_border_f11[y+data.height*z];
data.f12_new[offset] = data.nb_border_f12[y+data.height*z];
data.f13_new[offset] = data.nb_border_f13[y+data.height*z];
data.f14_new[offset] = data.nb_border_f14[y+data.height*z];
}
}
extern "C"
void apply_border_3d ( DataStruct3d *data, dim3 grid, dim3 block )
{
apply_border_3d_kernel<<<grid, block, 0, data->stream>>> ( *data );
CUT_CHECK_ERROR("apply border failed");
}
extern "C"
void apply_border_cpu ( DataStructCpu *data )
{
int x, y, offset;
for (x = 0; x < data->width; x++)
{
// If the lattice node is the most right it copies f1, f5 and f8 neighbor borders
if (x == 0) for (y = 0; y < data->height; y++)
{
offset = x + y*data->width;
data->f1_new[offset] = data->border_f1[y];
data->f5_new[offset] = data->border_f5[y];
data->f8_new[offset] = data->border_f8[y];
}
// If the lattice node is the most left it copies f3, f6 and f7 neighbor borders
if (x == (data->width-1)) for (y = 0; y < data->height; y++)
{
offset = x + y*data->width;
data->f3_new[offset] = data->border_f3[y];
data->f6_new[offset] = data->border_f6[y];
data->f7_new[offset] = data->border_f7[y];
}
}
}
extern "C"
void apply_border_3d_cpu ( DataStruct3dCpu *data )
{
int x, y, z, offset, border_offset;
int width = data->width;
int height = data->height;
int length = data->length;
for (x = 0; x < width; x++)
{
// if x is the most right node copies (f1, f5 and f8) fx_new to border_fx
if (x == 0) for (z=0; z < length; z++) for (y = 0; y < height; y++)
{
offset = x + y*width + z*width*height;
border_offset = y + z*height;
data->f1_new [offset] = data->border_f1 [border_offset];
data->f7_new [offset] = data->border_f7 [border_offset];
data->f8_new [offset] = data->border_f8 [border_offset];
data->f9_new [offset] = data->border_f9 [border_offset];
data->f10_new[offset] = data->border_f10[border_offset];
}
// if x is the most left node copies (f3, f6 and f7) fx_new to border_fx
if (x == (width-1)) for (z=0; z < length; z++) for (y = 0; y < height; y++)
85
{
offset = x + y*width + z*width*height;
border_offset = y + z*height;
data->f2_new [offset] = data->border_f2 [border_offset];
data->f11_new[offset] = data->border_f11[border_offset];
data->f12_new[offset] = data->border_f12[border_offset];
data->f13_new[offset] = data->border_f13[border_offset];
data->f14_new[offset] = data->border_f14[border_offset];
}
}
}
86
collision.h:
/**************************************************************************************************
* Lattice Boltzmann Multi-GPU CUDA Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* This header file defines the collision function. This functions calculate the collison term
* and pass the new distribution function to fa
**************************************************************************************************/
#ifndef COLL_H
#define COLL_H
extern "C"
void collision(DataStruct *data, float w0, float w1, float w5, float tau_inv, dim3 grid, dim3 block );
extern "C"
void collision_3d( DataStruct3d *data, float w0, float w1, float w5, float tau_inv, dim3 grid, dim3 block
);
extern "C"
void collision_cpu( DataStructCpu *data, float w0, float w1, float w5, float tau_inv );
extern "C"
void collision_3d_cpu( DataStruct3dCpu *data, float w0, float w1, float w5, float tau_inv );
#endif
collision.cu:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* Collision C and CUDA kernel functions
**************************************************************************************************/
#include <cutil.h>
#include "latibol.h"
__global__ void collision_kernel ( DataStruct data, float w0, float w1, float w5, float tau_inv )
{
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
int offset = x + y * blockDim.x * gridDim.x;
if (!data.solid_bound[offset])
{
// Create registers
float rho, ux, uy, usq, uxsq, uysq;
float uxuy5, uxuy6;
//Apply Bounce-Back boundary condition
if (data.solid[offset])
{
data.f1[offset] = data.f3_new[offset];
data.f2[offset] = data.f4_new[offset];
data.f3[offset] = data.f1_new[offset];
data.f4[offset] = data.f2_new[offset];
data.f5[offset] = data.f7_new[offset];
data.f6[offset] = data.f8_new[offset];
data.f7[offset] = data.f5_new[offset];
data.f8[offset] = data.f6_new[offset];
}
else
{
// Calculate macroscopic density
rho = data.f0_new[offset] + data.f1_new[offset] + data.f2_new[offset] +
data.f3_new[offset] + data.f4_new[offset] + data.f5_new[offset] +
data.f6_new[offset] + data.f7_new[offset] + data.f8_new[offset];
// Calculate macroscopic velocity in x
ux = ( data.f1_new[offset] - data.f3_new[offset] + data.f5_new[offset] -
data.f6_new[offset] - data.f7_new[offset] + data.f8_new[offset] ) / rho;
// Calculate macroscopic velocity in y
87
uy = ( data.f2_new[offset] - data.f4_new[offset] + data.f5_new[offset] +
data.f6_new[offset] - data.f7_new[offset] - data.f8_new[offset] ) / rho;
// Calculate square velocity and macroscopic velocity
uxsq = ux*ux;
uysq = uy*uy;
usq = uxsq + uysq;
data.u[offset] = sqrtf(usq);
// Pre-calculate some values
w0 = w0*rho;
w1 = w1*rho;
w5 = w5*rho;
uxuy5 = +ux+uy;
uxuy6 = -ux+uy;
// Inlet BC - very crude
// Calculate the collision term and pass the new equilibrium distribution function
data.f0[offset] = data.f0_new[offset] - tau_inv*( data.f0_new[offset] -
w0*(1.f - 1.5f*usq) );
data.f1[offset] = data.f1_new[offset] - tau_inv*( data.f1_new[offset] -
w1*(1.f + 3.f*ux + 4.5f*uxsq - 1.5f*usq) );
data.f2[offset] = data.f2_new[offset] - tau_inv*( data.f2_new[offset] -
w1*(1.f + 3.f*uy + 4.5f*uysq - 1.5f*usq) );
data.f3[offset] = data.f3_new[offset] - tau_inv*( data.f3_new[offset] -
w1*(1.f - 3.f*ux + 4.5f*uxsq - 1.5f*usq) );
data.f4[offset] = data.f4_new[offset] - tau_inv*( data.f4_new[offset] -
w1*(1.f - 3.f*uy + 4.5f*uysq - 1.5f*usq) );
data.f5[offset] = data.f5_new[offset] - tau_inv*( data.f5_new[offset] -
w5*(1.f + 3.f*uxuy5 + 4.5f*uxuy5*uxuy5 - 1.5f*usq) );
data.f6[offset] = data.f6_new[offset] - tau_inv*( data.f6_new[offset] -
w5*(1.f + 3.f*uxuy6 + 4.5f*uxuy6*uxuy6 - 1.5f*usq) );
data.f7[offset] = data.f7_new[offset] - tau_inv*( data.f7_new[offset] -
w5*(1.f - 3.f*uxuy5 + 4.5f*uxuy5*uxuy5 - 1.5f*usq) );
data.f8[offset] = data.f8_new[offset] - tau_inv*( data.f8_new[offset] -
w5*(1.f - 3.f*uxuy6 + 4.5f*uxuy6*uxuy6 - 1.5f*usq) );
}
}
}
// C collision function that calls CUDA kernel
extern "C"
void collision(DataStruct *data, float w0, float w1, float w5, float tau_inv, dim3 grid, dim3 block )
{
collision_kernel<<<grid, block, 0, data->stream>>> ( *data, w0, w1, w5, tau_inv );
CUT_CHECK_ERROR("collision failed");
}
__global__ void collision_3d_kernel ( DataStruct3d data, float w0, float w1, float w7, float tau_inv )
{
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
int z = threadIdx.z + blockIdx.z * blockDim.z;
int offset = x + y*blockDim.x*gridDim.x + z*blockDim.x*gridDim.x*blockDim.y*gridDim.y;
if (!data.solid_bound[offset])
{
// Create registers
float rho, ux, uy, uz, usq, uxsq, uysq, uzsq;
float uxuy7, uxuy8, uxuz9, uxuz10, uyuz15, uyuz16;
//Apply Bounce-Back boundary condition
if (data.solid[offset])
{
data.f1 [offset] = data.f2_new [offset];
data.f2 [offset] = data.f1_new [offset];
data.f3 [offset] = data.f4_new [offset];
data.f4 [offset] = data.f3_new [offset];
data.f5 [offset] = data.f6_new [offset];
data.f6 [offset] = data.f5_new [offset];
data.f7 [offset] = data.f12_new[offset];
data.f8 [offset] = data.f11_new[offset];
data.f9 [offset] = data.f14_new[offset];
data.f10[offset] = data.f13_new[offset];
data.f11[offset] = data.f8_new [offset];
data.f12[offset] = data.f7_new [offset];
data.f13[offset] = data.f10_new[offset];
data.f14[offset] = data.f9_new [offset];
data.f15[offset] = data.f18_new[offset];
data.f16[offset] = data.f17_new[offset];
data.f17[offset] = data.f16_new[offset];
data.f18[offset] = data.f15_new[offset];
}
else
{
// Calculate macroscopic density
88
rho = data.f0_new[offset] + data.f1_new[offset] + data.f2_new[offset] +
data.f3_new[offset] + data.f4_new[offset] + data.f5_new[offset] +
data.f6_new[offset] + data.f7_new[offset] + data.f8_new[offset] +
data.f9_new[offset] + data.f10_new[offset] + data.f11_new[offset] +
data.f12_new[offset] + data.f13_new[offset] + data.f14_new[offset] +
data.f15_new[offset] + data.f16_new[offset] + data.f17_new[offset] +
data.f18_new[offset];
// Calculate macroscopic velocity in x
ux = ( +data.f1_new [offset] +data.f7_new [offset] +data.f8_new [offset]
+data.f9_new [offset] +data.f10_new[offset] -data.f2_new [offset]
-data.f11_new[offset] -data.f12_new[offset] -data.f13_new[offset]
-data.f14_new[offset] ) / rho;
// Calculate macroscopic velocity in y
uy = ( +data.f3_new [offset] +data.f7_new [offset] +data.f11_new[offset]
+data.f15_new[offset] +data.f16_new[offset] -data.f4_new [offset]
-data.f8_new [offset] -data.f12_new[offset] -data.f17_new[offset]
-data.f18_new[offset] ) / rho;
// Calculate macroscopic velocity in z
uz = ( +data.f5_new [offset] +data.f9_new [offset] +data.f13_new[offset]
+data.f15_new[offset] +data.f17_new[offset] -data.f6_new [offset]
-data.f10_new[offset] -data.f14_new[offset] -data.f16_new[offset]
-data.f18_new[offset] ) / rho;
// Calculate square velocity and macroscopic velocity
uxsq = ux*ux;
uysq = uy*uy;
uzsq = uz*uz;
usq = uxsq + uysq + uzsq;
data.u[offset] = sqrtf(usq);
// Pre-calculate some values
w0 = w0*rho;
w1 = w1*rho;
w7 = w7*rho;
uxuy7 = +ux +uy;
uxuy8 = +ux -uy;
uxuz9 = +ux +uz;
uxuz10 = +ux -uz;
uyuz15 = +uy +uz;
uyuz16 = +uy -uz;
data.f0 [offset] = data.f0_new [offset] - tau_inv*( data.f0_new [offset] -
w0*(1.f - 1.5f*usq) );
data.f1 [offset] = data.f1_new [offset] - tau_inv*( data.f1_new [offset] -
w1*(1.f + 3.f*ux + 4.5f*uxsq - 1.5f*usq) );
data.f2 [offset] = data.f2_new [offset] - tau_inv*( data.f2_new [offset] -
w1*(1.f - 3.f*ux + 4.5f*uxsq - 1.5f*usq) );
data.f3 [offset] = data.f3_new [offset] - tau_inv*( data.f3_new [offset] -
w1*(1.f + 3.f*uy + 4.5f*uysq - 1.5f*usq));
data.f4 [offset] = data.f4_new [offset] - tau_inv*( data.f4_new [offset] -
w1*(1.f - 3.f*uy + 4.5f*uysq - 1.5f*usq) );
data.f5 [offset] = data.f5_new [offset] - tau_inv*( data.f5_new [offset] -
w1*(1.f + 3.f*uz + 4.5f*uzsq - 1.5f*usq) );
data.f6 [offset] = data.f6_new [offset] - tau_inv*( data.f6_new [offset] -
w1*(1.f - 3.f*uz + 4.5f*uzsq - 1.5f*usq) );
data.f7 [offset] = data.f7_new [offset] - tau_inv*( data.f7_new [offset] -
w7*(1.f + 3.f*uxuy7 + 4.5f*uxuy7 *uxuy7 - 1.5f*usq) );
data.f8 [offset] = data.f8_new [offset] - tau_inv*( data.f8_new [offset] -
w7*(1.f + 3.f*uxuy8 + 4.5f*uxuy8 *uxuy8 - 1.5f*usq) );
data.f9 [offset] = data.f9_new [offset] - tau_inv*( data.f9_new [offset] -
w7*(1.f + 3.f*uxuz9 + 4.5f*uxuz9 *uxuz9 - 1.5f*usq) );
data.f10[offset] = data.f10_new[offset] - tau_inv*( data.f10_new[offset] -
w7*(1.f + 3.f*uxuz10 + 4.5f*uxuz10*uxuz10 - 1.5f*usq) );
data.f11[offset] = data.f11_new[offset] - tau_inv*( data.f11_new[offset] -
w7*(1.f - 3.f*uxuy8 + 4.5f*uxuy8 *uxuy8 - 1.5f*usq) );
data.f12[offset] = data.f12_new[offset] - tau_inv*( data.f12_new[offset] -
w7*(1.f - 3.f*uxuy7 + 4.5f*uxuy7 *uxuy7 - 1.5f*usq) );
data.f13[offset] = data.f13_new[offset] - tau_inv*( data.f13_new[offset] -
w7*(1.f - 3.f*uxuz10 + 4.5f*uxuz10*uxuz10 - 1.5f*usq) );
data.f14[offset] = data.f14_new[offset] - tau_inv*( data.f14_new[offset] -
w7*(1.f - 3.f*uxuz9 + 4.5f*uxuz9 *uxuz9 - 1.5f*usq) );
data.f15[offset] = data.f15_new[offset] - tau_inv*( data.f15_new[offset] -
w7*(1.f + 3.f*uyuz15 + 4.5f*uyuz15*uyuz15 - 1.5f*usq) );
data.f16[offset] = data.f16_new[offset] - tau_inv*( data.f16_new[offset] -
w7*(1.f + 3.f*uyuz16 + 4.5f*uyuz16*uyuz16 - 1.5f*usq) );
data.f17[offset] = data.f17_new[offset] - tau_inv*( data.f17_new[offset] -
w7*(1.f - 3.f*uyuz16 + 4.5f*uyuz16*uyuz16 - 1.5f*usq) );
data.f18[offset] = data.f18_new[offset] - tau_inv*( data.f18_new[offset] -
w7*(1.f - 3.f*uyuz15 + 4.5f*uyuz15*uyuz15 - 1.5f*usq) );
}
}
}
89
// C collision function that calls CUDA kernel
extern "C"
void collision_3d( DataStruct3d *data, float w0, float w1, float w7, float tau_inv, dim3 grid, dim3 block )
{
collision_3d_kernel<<<grid, block, 0, data->stream>>> ( *data, w0, w1, w7, tau_inv );
CUT_CHECK_ERROR("collision failed");
}
extern "C"
void collision_cpu( DataStructCpu *data, float w0, float w1, float w5, float tau_inv )
{
int offset;
float rho, ux, uy, usq, uxsq, uysq;
float uxuy5, uxuy6;
float w0_r, w1_r, w5_r;
for (offset = 0; offset < data->width*data->height; offset++) if (!data->solid_bound[offset])
{
//Apply Bounce-Back boundary condition
if (data->solid[offset])
{
data->f1[offset] = data->f3_new[offset];
data->f2[offset] = data->f4_new[offset];
data->f3[offset] = data->f1_new[offset];
data->f4[offset] = data->f2_new[offset];
data->f5[offset] = data->f7_new[offset];
data->f6[offset] = data->f8_new[offset];
data->f7[offset] = data->f5_new[offset];
data->f8[offset] = data->f6_new[offset];
}
else
{
// Calculate macroscopic density
rho = data->f0_new[offset] + data->f1_new[offset] + data->f2_new[offset] +
data->f3_new[offset] + data->f4_new[offset] + data->f5_new[offset] +
data->f6_new[offset] + data->f7_new[offset] + data->f8_new[offset];
// Calculate macroscopic velocity in x
ux = ( data->f1_new[offset] - data->f3_new[offset] + data->f5_new[offset] -
data->f6_new[offset] - data->f7_new[offset] + data->f8_new[offset] ) / rho;
// Calculate macroscopic velocity in y
uy = ( data->f2_new[offset] - data->f4_new[offset] + data->f5_new[offset] +
data->f6_new[offset] - data->f7_new[offset] - data->f8_new[offset] ) / rho;
// Calculate square velocity and macroscopic velocity
uxsq = ux*ux;
uysq = uy*uy;
usq = uxsq + uysq;
data->u[offset] = sqrtf(usq);
// Pre-calculate some values
w0_r = w0*rho;
w1_r = w1*rho;
w5_r = w5*rho;
uxuy5 = +ux+uy;
uxuy6 = -ux+uy;
// If the node is not solid calculate the collison term and pass the new distribution
// function to fx
data->f0[offset] = data->f0_new[offset] - tau_inv*( data->f0_new[offset] -
w0_r*(1.f - 1.5f*usq) );
data->f1[offset] = data->f1_new[offset] - tau_inv*( data->f1_new[offset] -
w1_r*(1.f + 3.f*ux + 4.5f*uxsq - 1.5f*usq) );
data->f2[offset] = data->f2_new[offset] - tau_inv*( data->f2_new[offset] -
w1_r*(1.f + 3.f*uy + 4.5f*uysq - 1.5f*usq) );
data->f3[offset] = data->f3_new[offset] - tau_inv*( data->f3_new[offset] -
w1_r*(1.f - 3.f*ux + 4.5f*uxsq - 1.5f*usq) );
data->f4[offset] = data->f4_new[offset] - tau_inv*( data->f4_new[offset] -
w1_r*(1.f - 3.f*uy + 4.5f*uysq - 1.5f*usq) );
data->f5[offset] = data->f5_new[offset] - tau_inv*( data->f5_new[offset] -
w5_r*(1.f + 3.f*uxuy5 + 4.5f*uxuy5*uxuy5 - 1.5f*usq) );
data->f6[offset] = data->f6_new[offset] - tau_inv*( data->f6_new[offset] -
w5_r*(1.f + 3.f*uxuy6 + 4.5f*uxuy6*uxuy6 - 1.5f*usq) );
data->f7[offset] = data->f7_new[offset] - tau_inv*( data->f7_new[offset] -
w5_r*(1.f - 3.f*uxuy5 + 4.5f*uxuy5*uxuy5 - 1.5f*usq) );
data->f8[offset] = data->f8_new[offset] - tau_inv*( data->f8_new[offset] -
w5_r*(1.f - 3.f*uxuy6 + 4.5f*uxuy6*uxuy6 - 1.5f*usq) );
}
}
}
extern "C"
void collision_3d_cpu( DataStruct3dCpu *data, float w0, float w1, float w7, float tau_inv )
{
int offset;
float w0_r, w1_r, w7_r;
90
float rho, ux, uy, uz, usq, uxsq, uysq, uzsq;
float uxuy7, uxuy8, uxuz9, uxuz10, uyuz15, uyuz16;
for (offset = 0; offset < data->width*data->height*data->length; offset++) if (!data-
>solid_bound[offset])
{
//Apply Bounce-Back boundary condition
if (data->solid[offset])
{
data->f1 [offset] = data->f2_new [offset];
data->f2 [offset] = data->f1_new [offset];
data->f3 [offset] = data->f4_new [offset];
data->f4 [offset] = data->f3_new [offset];
data->f5 [offset] = data->f6_new [offset];
data->f6 [offset] = data->f5_new [offset];
data->f7 [offset] = data->f12_new[offset];
data->f8 [offset] = data->f11_new[offset];
data->f9 [offset] = data->f14_new[offset];
data->f10[offset] = data->f13_new[offset];
data->f11[offset] = data->f8_new [offset];
data->f12[offset] = data->f7_new [offset];
data->f13[offset] = data->f10_new[offset];
data->f14[offset] = data->f9_new [offset];
data->f15[offset] = data->f18_new[offset];
data->f16[offset] = data->f17_new[offset];
data->f17[offset] = data->f16_new[offset];
data->f18[offset] = data->f15_new[offset];
}
else
{
// Calculate macroscopic density
rho = data->f0_new[offset] + data->f1_new[offset] + data->f2_new[offset] +
data->f3_new[offset] + data->f4_new[offset] + data->f5_new[offset] +
data->f6_new[offset] + data->f7_new[offset] + data->f8_new[offset] +
data->f9_new[offset] + data->f10_new[offset] + data->f11_new[offset] +
data->f12_new[offset] + data->f13_new[offset] + data->f14_new[offset] +
data->f15_new[offset] + data->f16_new[offset] + data->f17_new[offset] +
data->f18_new[offset];
// Calculate macroscopic velocity in x
ux = ( +data->f1_new [offset] +data->f7_new [offset] +data->f8_new [offset]
+data->f9_new [offset] +data->f10_new[offset] -data->f2_new [offset]
-data->f11_new[offset] -data->f12_new[offset] -data->f13_new[offset]
-data->f14_new[offset] ) / rho;
// Calculate macroscopic velocity in y
uy = ( +data->f3_new [offset] +data->f7_new [offset] +data->f11_new[offset]
+data->f15_new[offset] +data->f16_new[offset] -data->f4_new [offset]
-data->f8_new [offset] -data->f12_new[offset] -data->f17_new[offset]
-data->f18_new[offset] ) / rho;
// Calculate macroscopic velocity in z
uz = ( +data->f5_new [offset] +data->f9_new [offset] +data->f13_new[offset]
+data->f15_new[offset] +data->f17_new[offset] -data->f6_new [offset]
-data->f10_new[offset] -data->f14_new[offset] -data->f16_new[offset]
-data->f18_new[offset] ) / rho;
// Calculate square velocity and macroscopic velocity
uxsq = ux*ux;
uysq = uy*uy;
uzsq = uz*uz;
usq = uxsq + uysq + uzsq;
data->u[offset] = sqrtf(usq);
// Pre-calculate some values
w0_r = w0*rho;
w1_r = w1*rho;
w7_r = w7*rho;
uxuy7 = +ux +uy;
uxuy8 = +ux -uy;
uxuz9 = +ux +uz;
uxuz10 = +ux -uz;
uyuz15 = +uy +uz;
uyuz16 = +uy -uz;
// If the node is not solid calculate the collison term and pass the new distribution
// function to fx
data->f0 [offset] = data->f0_new [offset] - tau_inv*( data->f0_new [offset] -
w0_r*(1.f - 1.5f*usq) );
data->f1 [offset] = data->f1_new [offset] - tau_inv*( data->f1_new [offset] -
w1_r*(1.f + 3.f*ux + 4.5f*uxsq - 1.5f*usq) );
data->f2 [offset] = data->f2_new [offset] - tau_inv*( data->f2_new [offset] -
w1_r*(1.f - 3.f*ux + 4.5f*uxsq - 1.5f*usq) );
data->f3 [offset] = data->f3_new [offset] - tau_inv*( data->f3_new [offset] -
w1_r*(1.f + 3.f*uy + 4.5f*uysq - 1.5f*usq));
data->f4 [offset] = data->f4_new [offset] - tau_inv*( data->f4_new [offset] -
91
w1_r*(1.f - 3.f*uy + 4.5f*uysq - 1.5f*usq) );
data->f5 [offset] = data->f5_new [offset] - tau_inv*( data->f5_new [offset] -
w1_r*(1.f + 3.f*uz + 4.5f*uzsq - 1.5f*usq) );
data->f6 [offset] = data->f6_new [offset] - tau_inv*( data->f6_new [offset] -
w1_r*(1.f - 3.f*uz + 4.5f*uzsq - 1.5f*usq) );
data->f7 [offset] = data->f7_new [offset] - tau_inv*( data->f7_new [offset] -
w7_r*(1.f + 3.f*uxuy7 + 4.5f*uxuy7 *uxuy7 - 1.5f*usq) );
data->f8 [offset] = data->f8_new [offset] - tau_inv*( data->f8_new [offset] -
w7_r*(1.f + 3.f*uxuy8 + 4.5f*uxuy8 *uxuy8 - 1.5f*usq) );
data->f9 [offset] = data->f9_new [offset] - tau_inv*( data->f9_new [offset] -
w7_r*(1.f + 3.f*uxuz9 + 4.5f*uxuz9 *uxuz9 - 1.5f*usq) );
data->f10[offset] = data->f10_new[offset] - tau_inv*( data->f10_new[offset] -
w7_r*(1.f + 3.f*uxuz10 + 4.5f*uxuz10*uxuz10 - 1.5f*usq) );
data->f11[offset] = data->f11_new[offset] - tau_inv*( data->f11_new[offset] -
w7_r*(1.f - 3.f*uxuy8 + 4.5f*uxuy8 *uxuy8 - 1.5f*usq) );
data->f12[offset] = data->f12_new[offset] - tau_inv*( data->f12_new[offset] -
w7_r*(1.f - 3.f*uxuy7 + 4.5f*uxuy7 *uxuy7 - 1.5f*usq) );
data->f13[offset] = data->f13_new[offset] - tau_inv*( data->f13_new[offset] -
w7_r*(1.f - 3.f*uxuz10 + 4.5f*uxuz10*uxuz10 - 1.5f*usq) );
data->f14[offset] = data->f14_new[offset] - tau_inv*( data->f14_new[offset] -
w7_r*(1.f - 3.f*uxuz9 + 4.5f*uxuz9 *uxuz9 - 1.5f*usq) );
data->f15[offset] = data->f15_new[offset] - tau_inv*( data->f15_new[offset] -
w7_r*(1.f + 3.f*uyuz15 + 4.5f*uyuz15*uyuz15 - 1.5f*usq) );
data->f16[offset] = data->f16_new[offset] - tau_inv*( data->f16_new[offset] -
w7_r*(1.f + 3.f*uyuz16 + 4.5f*uyuz16*uyuz16 - 1.5f*usq) );
data->f17[offset] = data->f17_new[offset] - tau_inv*( data->f17_new[offset] -
w7_r*(1.f - 3.f*uyuz16 + 4.5f*uyuz16*uyuz16 - 1.5f*usq) );
data->f18[offset] = data->f18_new[offset] - tau_inv*( data->f18_new[offset] -
w7_r*(1.f - 3.f*uyuz15 + 4.5f*uyuz15*uyuz15 - 1.5f*usq) );
}
}
}
92
save.h:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 28.11.2012
* UNICAMP - Universidade Estadual de Campinas
*
* This header file defines the psave and save functions. The psave function saves the parallel
* file, that is used to unite all the datasets from each device save file.* Each device saves
* its dataset file when it's called the save function.
**************************************************************************************************/
#ifndef SAVE_H
#define SAVE_H
extern "C"
void psave(int width, int height, int length, int step, int deviceCount, int commSize);
extern "C"
void *save(void *w);
#endif
save.cu:
/**************************************************************************************************
* D2Q9 Lattice Boltzmann CUDA Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* Save functions
**************************************************************************************************/
#include <cutil.h>
#include <cuda_runtime_api.h>
#include <pthread.h>
#include <error.h>
#include "latibol.h"
extern "C"
void psave(int width, int height, int length, int step, int deviceCount, int commSize)
{
// Create variables
int i;
FILE *psave_file;
char *name;
// Allocate name string
name = (char *)malloc(13*sizeof(char));
// Get the name 'rxxxx.pvti' of save file according to the number of the step
name[0] = 'r';
name[1] = (char)(step / 1000) + '0';
name[2] = (char)(step % 1000 / 100) + '0';
name[3] = (char)(step % 100 / 10) + '0';
name[4] = (char)(step % 10) + '0';
name[5] = '.';
name[6] = 'p';
name[7] = 'v';
name[8] = 't';
name[9] = 'i';
name[10] = '\0';
// Create and open 'rxxxx.pvti' file
printf("%s ", name);
psave_file=fopen(name,"w");
// Change name string to name of the dataset file
name[5] = 'p';
name[8] = '.';
name[9] = 'v';
name[10] = 't';
name[11] = 'i';
name[12] = '\0';
// Save the parallel file according to VTK file format
fprintf(psave_file, "<VTKFile type=\"PImageData\" version=\"0.1\" byte_order=\"LittleEndian\">\n");
fprintf(psave_file, " <PImageData WholeExtent=\"0 %d 0 %d 0 %d\" GhostLevel=\"#\" Origin=\"0 0 0\""
" Spacing=\"1 1 1\">\n", (width - 1)*commSize*deviceCount, height-1, length-1);
93
fprintf(psave_file, " <PPointData Scalars=\"Velocity\">\n");
fprintf(psave_file, " <DataArray type=\"Float32\" Name=\"Velocity\" format=\"ascii\"/>\n");
fprintf(psave_file, " </PPointData>\n <PCellData>\n </PCellData>\n");
for (i=0; i < commSize*deviceCount; i++) {
name[6] = (char)(i / 10) + '0';
name[7] = (char)(i % 10) + '0';
fprintf(psave_file, " <Piece Extent=\"%d %d 0 %d 0 %d\" Source=\"%s\"/>\n",
i*(width-1), (width-1) + i*(width-1), height-1, length-1, name);
}
fprintf(psave_file, " </PImageData>\n</VTKFile>\n");
// Close file and deallocate name string
fclose (psave_file);
free( name );
}
extern "C"
void *save(void *w)
{
SaveStruct *saveData = (SaveStruct *) w;
int i;
int width = saveData->width;
int height = saveData->height;
int length = saveData->length;
int step = saveData->step;
int commRank = saveData->commRank;
int deviceID = saveData->deviceID;
int deviceCount = saveData->deviceCount;
FILE *save_file;
// Get the name 'rxxxxpxx.vti' of save file according to the number of the step and device
saveData->name[0] = 'r';
saveData->name[1] = (char)(step / 1000) + '0';
saveData->name[2] = (char)(step % 1000 / 100) + '0';
saveData->name[3] = (char)(step % 100 / 10) + '0';
saveData->name[4] = (char)(step % 10) + '0';
saveData->name[5] = 'p';
saveData->name[6] = (char)( (commRank*deviceCount+deviceID) / 10 ) + '0';
saveData->name[7] = (char)( (commRank*deviceCount+deviceID) % 10 ) + '0';
saveData->name[8] = '.';
saveData->name[9] = 'v';
saveData->name[10] = 't';
saveData->name[11] = 'i';
saveData->name[12] = '\0';
// Create and open 'rxxxxpxx.vti' file
save_file = fopen(saveData->name,"w");
// Save dataset file according to VTK file format
fprintf(save_file, "<VTKFile type=\"ImageData\" version=\"0.1\" byte_order=\"LittleEndian\">\n");
fprintf(save_file, " <ImageData WholeExtent=\"%d %d 0 %d 0 %d\" Origin=\"0 0 0\" Spacing=\"1 1
1\">\n",
(commRank*deviceCount+deviceID)*(width-1), width-1 + (commRank*deviceCount+deviceID)*(width-1),
height-1, length-1);
fprintf(save_file, " <Piece Extent=\"%d %d 0 %d 0 %d\">\n",
(commRank*deviceCount+deviceID)*(width-1), width-1 + (commRank*deviceCount+deviceID)*(width-1),
height-1, length-1);
fprintf(save_file, " <PointData Scalars=\"Velocity\">\n");
fprintf(save_file, " <DataArray type=\"Float32\" Name=\"Velocity\" format=\"ascii\" >\n");
fprintf(save_file, " ");
// Save dataset to file.
// If the lattice node is solid, the save data is 0.0, else, save data is the velocity
for(i=0; i < width*height*length; i++)
{
if (!saveData->solid[i]) fprintf(save_file, "%f ",saveData->u[i]);
else fprintf(save_file, "%f ", 0.0);
if ((i+1) % width == 0) fprintf(save_file, "\n ");
}
fprintf(save_file, "</DataArray>\n </PointData>\n <CellData>\n </CellData>\n </Piece>\n "
"</ImageData>\n</VTKFile>\n");
// Close file and deallocate host variables
fclose( save_file );
return 0;
}
94
cudaFree_data.h:
/**************************************************************************************************
* Lattice Boltzmann Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* This header file defines the cudaFree_data function. This functions desallocate all memory
* at the device.
**************************************************************************************************/
#ifndef CUDAFREE_H
#define CUDAFREE_H
extern "C"
void cudaFree_data( DataStruct *data, int deviceCount );
extern "C"
void cudaFree_data_3d( DataStruct3d *data, int deviceCount );
#endif
cudaFree_data.cu:
/**************************************************************************************************
* D2Q9 Lattice Boltzmann Multi-GPU CUDA Simulator
* Lucas Volpe - 11.28.2012
* UNICAMP - Universidade Estadual de Campinas
*
* CudaFree_data function
**************************************************************************************************/
#include <cutil.h>
#include <cuda_runtime_api.h>
#include "latibol.h"
extern "C"
void cudaFree_data( DataStruct *data, int deviceCount )
{
int i;
for (i=0; i < deviceCount ; i++)
{
// Set current device and desallocate memories
CUDA_SAFE_CALL( cudaSetDevice( i ) );
CUDA_SAFE_CALL( cudaFree( data[i].u ) );
CUDA_SAFE_CALL( cudaFree( data[i].solid ) );
CUDA_SAFE_CALL( cudaFree( data[i].solid_bound ) );
CUDA_SAFE_CALL( cudaFree( data[i].f0 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f1 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f2 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f3 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f4 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f5 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f6 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f7 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f8 ) );
CUDA_SAFE_CALL(cudaFree( data[i].f0_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f1_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f2_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f3_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f4_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f5_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f6_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f7_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f8_new ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f1 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f5 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f8 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f3 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f6 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f7 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f1 ) );
95
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f5 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f8 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f3 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f6 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f7 ) );
CUDA_SAFE_CALL( cudaStreamDestroy( data[i].stream ) );
}
}
extern "C"
void cudaFree_data_3d( DataStruct3d *data, int deviceCount )
{
int i;
for (i=0; i < deviceCount ; i++)
{
// Set current device and desallocate memories
CUDA_SAFE_CALL( cudaSetDevice( i ) );
CUDA_SAFE_CALL( cudaFree( data[i].u ) );
CUDA_SAFE_CALL( cudaFree( data[i].solid ) );
CUDA_SAFE_CALL( cudaFree( data[i].solid_bound ) );
CUDA_SAFE_CALL( cudaFree( data[i].f0 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f1 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f2 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f3 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f4 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f5 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f6 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f7 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f8 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f9 ) );
CUDA_SAFE_CALL( cudaFree( data[i].f10) );
CUDA_SAFE_CALL( cudaFree( data[i].f11) );
CUDA_SAFE_CALL( cudaFree( data[i].f12) );
CUDA_SAFE_CALL( cudaFree( data[i].f13) );
CUDA_SAFE_CALL( cudaFree( data[i].f14) );
CUDA_SAFE_CALL( cudaFree( data[i].f15) );
CUDA_SAFE_CALL( cudaFree( data[i].f16) );
CUDA_SAFE_CALL( cudaFree( data[i].f17) );
CUDA_SAFE_CALL( cudaFree( data[i].f18) );
CUDA_SAFE_CALL(cudaFree( data[i].f0_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f1_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f2_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f3_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f4_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f5_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f6_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f7_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f8_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f9_new ) );
CUDA_SAFE_CALL(cudaFree( data[i].f10_new) );
CUDA_SAFE_CALL(cudaFree( data[i].f11_new) );
CUDA_SAFE_CALL(cudaFree( data[i].f12_new) );
CUDA_SAFE_CALL(cudaFree( data[i].f13_new) );
CUDA_SAFE_CALL(cudaFree( data[i].f14_new) );
CUDA_SAFE_CALL(cudaFree( data[i].f15_new) );
CUDA_SAFE_CALL(cudaFree( data[i].f16_new) );
CUDA_SAFE_CALL(cudaFree( data[i].f17_new) );
CUDA_SAFE_CALL(cudaFree( data[i].f18_new) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f1 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f7 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f8 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f9 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f10) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f2 ) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f11) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f12) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f13) );
CUDA_SAFE_CALL( cudaFree( data[i].border_f14) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f1 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f7 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f8 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f9 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f10) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f2 ) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f11) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f12) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f13) );
CUDA_SAFE_CALL( cudaFree( data[i].nb_border_f14) );
CUDA_SAFE_CALL( cudaStreamDestroy( data[i].stream ) );
}
}
96