UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ DEPARTAMENTO ACADÊMICO DE COMPUTAÇÃO CURSO DE BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO

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

Download "UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ DEPARTAMENTO ACADÊMICO DE COMPUTAÇÃO CURSO DE BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO"

Transcrição

1 UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ DEPARTAMENTO ACADÊMICO DE COMPUTAÇÃO CURSO DE BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO JOÃO MARTINS DE QUEIROZ FILHO AJUSTE NAS DIMENSÕES DE KERNELS PARA DISPOSITIVOS ACELERADORES COM BASE NA ANÁLISE DE CARACTERÍSTICAS ARQUITETURAIS E NA UTILIZAÇÃO DE FERRAMENTA DE AUTOTUNING MONOGRAFIA CAMPO MOURÃO 2018

2 JOÃO MARTINS DE QUEIROZ FILHO AJUSTE NAS DIMENSÕES DE KERNELS PARA DISPOSITIVOS ACELERADORES COM BASE NA ANÁLISE DE CARACTERÍSTICAS ARQUITETURAIS E NA UTILIZAÇÃO DE FERRAMENTA DE AUTOTUNING Trabalho de Conclusão de Curso de Graduação apresentado à disciplina de Trabalho de Conclusão de Curso 2, do Curso de Bacharelado em Ciência da Computação do Departamento Acadêmico de Computação da Universidade Tecnológica Federal do Paraná, como requisito parcial para obtenção do título de Bacharel em Ciência da Computação. Orientador: Prof. Dr. Rogério Aparecido Gonçalves CAMPO MOURÃO 2018

3 Ministério da Educação Universidade Tecnológica Federal do Paraná Câmpus Campo Mourão Curso de Bacharelado em Ciência da Computação ATA DE DEFESA DO TRABALHO DE CONCLUSÃO DE CURSO Às 13:50 do dia 20 de junho de 2018 foi realizada na sala E003 da UTFPR-CM a sessão pública da defesa do Trabalho de Conclusão do Curso de Bacharelado em Ciência da Computação do(a) acadêmico(a) João Martins de Queiroz Filho com o título Ajuste nas dimensões de Kernel para dispositivos aceleradores com base na análise de características arquiteturais e na utilização de ferramenta de autotuning. Estavam presentes, além do(a) acadêmico(a), os membros da banca examinadora composta por: Prof. Dr. Rogério Aparecido Gonçalves (orientador), Prof. Ms. Juliano Henrique Foleiss e Prof. Dr. Frank Helbert Borsato. Inicialmente, o(a) acadêmico(a) fez a apresentação do seu trabalho, sendo, em seguida, arguido(a) pela banca examinadora. Após as arguições, sem a presença do(a) acadêmico(a), a banca examinadora o(a) considerou na disciplina de Trabalho de Conclusão de Curso 2 e atribuiu, em consenso, a nota ( ). Este resultado foi comunicado ao(à) acadêmico(a) e aos presentes na sessão pública. A banca examinadora também comunicou ao acadêmico(a) que este resultado fica condicionado à entrega da versão final dentro dos padrões e da documentação exigida pela UTFPR ao professor Responsável do TCC no prazo de onze dias. Em seguida foi encerrada a sessão e, para constar, foi lavrada a presente Ata que segue assinada pelos membros da banca examinadora, após lida e considerada conforme. Observações: Campo Mourão, 20 de junho de 2018 Prof. Ms. Juliano Henrique Foleiss Prof. Dr. Frank Helbert Borsato Membro 1 Membro 2 Prof. Dr. Rogério Aparecido Gonçalves Orientador A ata de defesa assinada encontra-se na coordenação do curso. Câmpus Campo Mourão Via Rosalina Maria do Santos, 1233 CEP Caixa Postal: 271 Campo Mourão - PR - Brasil Telefone Geral +55 (44)

4 Agradecimentos Aos meus pais pelo amor, dedicação, sendo meus guias em todos os meus momentos, o apoio incondicional e motivação fizeram com que terminasse esta graduação. A Universidade Tecnológica Federal do Paraná, a todos os professores do Departamento de Ciência da Computação pela paciência e dedicação em ensinarem todo o conteúdo para sermos excelentes profissionais. Ao meu orientador, o professor Rogério Aparecido Gonçalves, que se tornou um amigo valioso e que tenho muito estimo, sem sua orientação e motivação não teria terminado esta Monografia. Aos amigos por terem me apoiado e ficarem ao meu lado nas horas que eu mais precisava. Em especial a uma amiga com nome de anjo que me apoiou e motivou nos momentos mais difíceis da graduação, a você todo meu carinho e admiração. A professora Vanessa Sehaber pela ajuda em Estatística. E a todos que direta ou indiretamente fizeram parta da minha formação, o meu muito obrigado.

5 Resumo Queiroz Filho, J. M. Ajuste nas dimensões de kernels para dispositivos aceleradores com base na análise de características arquiteturais e na utilização de ferramenta de Autotuning f. Monografia (Curso de Bacharelado em Ciência da Computação), Universidade Tecnológica Federal do Paraná. Campo Mourão, Atualmente há uma lacuna de desempenho entre Unidade Central de Processamento (CPU) e Unidade de Processamento Gráfico (GPU), esta lacuna fez com que desenvolvedores tenham o interesse por aplicações que exploram o trabalho conjunto entre CPUs e GPUs. Contudo para criar essas aplicações os desenvolvedores encontram desafios, desde transformar código legado para sistemas multicores até encontrar a configuração ideal para a arquitetura do dispositivo alvo. Desta forma, este trabalho tem como objetivo analisar por meio de ferramentas de autotuning de software a escolha de configurações para o arranjo de threads, na tentativa de extrair o melhor desempenho para dispositivos aceleradores, neste caso GPUs. Para alcançar esse objetivo foi utilizado um subconjunto de algoritmos da família Polybech, KernelGen e NVIDIA CUDA Samples que tiveram seus kernels testados com a ferramenta de autotuning OpenTuner. Os benchmarks foram executados e resultados de algumas métricas foram coletados utilizando a ferramenta de perfilamento nvprof da NVIDIA para a escolha da melhor configuração para cada contexto. Os resultados sugerem que o meio mais eficiente para se encontrar a melhor configuração para a arquitetura é utilizando ferramentas de autotuning, pois para determinados tamanhos torna-se inviável a escolha da configuração por meio de busca exaustiva ou por escolhas aleatórias. Palavras-chaves: Computação Paralela, Computação Heterogênea, GPGPU, otimização de código e ferramentas de autotuning

6 Abstract Queiroz Filho, J. M. Adjusting the dimensions of kernels for accelerator devices based on the analysis of architectural features and the use of the Autotuning tool f. Monograph (Undergradute Program in Computer Science), Federal University of Technology Paraná. Campo Mourão, PR, Brazil, Currently there is a performance gap between the Central Processing Unit (CPU) and the Graphics Processing Unit (GPU), this gap has made developers have an interest in applications that exploit the joint work between CPUs and GPUs. However, to create such applications developers face challenges ranging from transforming legacy code to multicore systems until they find the optimal configuration for the target device architecture. In this way, this work has as its objective to analyze the choice of configurations for the arrangement of threads. We are using software tools in an attempt to extract the best performance for device accelerators, in this case GPUs. To achieve this goal a set of algorithms of the Polybench, KernelGen and NVIDIA Samples were used which had their kernels tested with the OpenTuner autotuning tool. Benchmarks were executed and the results of some metrics were collected using nvprof NVIDIA s profiling tool to choose the best configuration for each context. The results suggest that the most efficient way to find the best configuration for the architecture is to use autotuning tools, because for certain sizes it becomes impracticable to choose the configuration through exhaustive search or random choices. Keywords: Parallel Computing. Heterogeneous Computing. GPGPU. code optimization and autotuning tools

7 Lista de figuras 1.1 Diferença de performance entre GPUs e CPUs. Fonte: (NVIDIA, 2017) Sistema heterogêneo CPU e FPGA. Fonte: (BRODTKORB et al., 2010) Layout lógico do coprocessador Intel Xeon Phi. Fonte: (RAHMAN, 2013) Modelo da arquitetura Fermi. Fonte: (NVIDIA, 2009) Fermi Streaming Multiprocessor (SM). Fonte: (NVIDIA, 2009) Diagrama completo arquitetura Kepler. Fonte: (NVIDIA, 2012) Kepler Streaming Multiprocessor (SM). Fonte: (NVIDIA, 2012) Arquitetura Pascal completa com 60 unidades Streaming Multiprocessor (SM ). Fonte: (NVIDIA, 2016) Pascal unidade SM. Fonte: (NVIDIA, 2016) Fluxo de execução de um código em Compute Unified Device Architecture (CUDA). Fonte: (CHENG et al., 2014) Divisão hierárquica de grids, blocos e threads. Fonte: (CHENG et al., 2014) Mapeamento da visão lógica para o hardware Organização do arranjo de threads Funcionamento do OpenTuner. Fonte: (ANSEL et al., 2014) Modelo de divisão de trabalho das técnicas de busca. Fonte: (ANSEL et al., 2014) Processo de geração da função objetiva. Fonte: (STORN, 1996) Funcionamento do algoritmo de busca Particle Swarm Optimization, em um espaço de busca 2D. Fonte: (ROBINSON; RAHMAT-SAMII, 2004) Demonstração do Algoritmo de busca Hill Climbing. Fonte: (RUSSELL; NORVIG, 2003) Etapas para a realização da pesquisa Arquiteturas que serão utilizadas nos experimentos Estatísticas de uso de instruções por ciclo. Fonte: (NVIDIA, 2015b) Comparação entre os testes e os valores encontrados para a métrica gld_efficiency Variação entre as configurações e a métrica achieved_occupancy para os dispositivos TitanX e GTX

8 5.4 Variação entre as configurações e a métrica achieved_occupancy para os dispositivos TitanX e GTX Valores encontrados segundo a métrica sm_efficiency Densidade de valores entre 80% a 100% Melhores resultados para a métrica sm_efficiency

9 Lista de tabelas 3.1 Configurações válidas para um determinado número de iterações Configurações válidas para o número de iterações N = Versões de kernels Número de operações realizadas para o cálculo do id global das threads Resultados do Experimento soma de vetor Média de warps ativos na execução do algoritmo sincos Média de warps ativos na execução do algoritmo sincos Sincos métrica gld_efficiency GPU-TitanX Sincos métrica gld_efficiency GPU-GTX Sincos métrica gld_efficiency GPU-GTX Sincos métrica gst_efficiency GPU-TitanX Sincos métrica gst_efficiency GPU-GTX Sincos métrica gst_efficiency GPU-GTX Sincos métrica inst_per_warp GPU-TitanX Sincos métrica inst_per_warp GPU-GTX Sincos métrica inst_per_warp GPU-GTX Sincos métrica ipc GPU-TitanX Sincos métrica ipc GPU-GTX Sincos métrica ipc GPU-GTX Sincos métrica achieved_occupancy GPU-TitanX Sincos métrica achieved_occupancy GPU-GTX Sincos métrica achieved_occupancy GPU-GTX Sincos métrica sm_efficiency GPU-TitanX Sincos métrica sm_efficiency GPU-GTX Sincos métrica sm_efficiency GPU-GTX GENERAL MATRIX MULTIPLY (GEMM ) métrica gld_efficiency GPU-TitanX GEMM métrica gld_efficiency GPU-GTX GEMM métrica gld_efficiency GPU-GTX GEMM métrica gst_efficiency GPU-TitanX GEMM métrica gst_efficiency GPU-GTX

10 5.27 GEMM métrica gst_efficiency GPU-GTX GEMM métrica inst_per_warp GPU-TitanX GEMM métrica inst_per_warp GPU-GTX GEMM métrica inst_per_warp GPU-GTX GEMM métrica ipc GPU-TitanX GEMM métrica ipc GPU-GTX GEMM métrica ipc GPU-GTX GEMM métrica occupancy GPU-TitanX GEMM métrica occupancy GPU-GTX GEMM métrica sm_efficiency GPU-TitanX GEMM métrica sm_efficiency GPU-GTX GEMM métrica sm_efficiency GPU-GTX Melhores configurações por força-bruta para a métrica sm_efficiency Dez Maiores e dez menores valores com base na métrica sm_efficiency Parte dos testes realizado por força-bruta para a métrica sm_efficiency Teste por força-bruta métrica sm_efficiency

11 Siglas API: Interface de Programação de Aplicativos ATLAS: Automatically Tunned Linear Algebra Software CPU: Unidade Central de Processamento CPUs: Unidades Centrais de Processamento CUDA: Compute Unified Device Architecture FPGA: Arranjo de Portas Programáveis FPGAs: Arranjos de Portas Programáveis GEMM: General Matrix Multiply GFLOPS: Bilhões de Operações em Ponto Flutuante por Segundo GPC: Graphics Processing Clusters GPU: Unidade de Processamento Gráfico GPUs: Unidades de Processamento Gráfico LLVM: Low Level Virtual Machine MIMD: Multiple Instruction, Multiple Data SFU: Unidades Funcionais Especiais SIMD: Single Instruction, Multiple Data SIMT: Single Instruction, Multiple Threads SISD: Single Instruction, Single Data SM: Streaming Multiprocessor TPC: Texture Processing Clusters

12 Sumário 1 Introdução Considerações preliminares Problema de Pesquisa Abordagem Proposta e Metodologia Objetivos Principais Contribuições Organização do Texto Referencial Teórico Modelos de programação paralela Dispositivos Aceleradores Arranjo de Portas Programáveis (FPGA) Intel Xeon Phi Unidades de Processamento Gráfico (GPU) Modelo de Programação (CUDA) Autotuning OpenTuner Considerações Finais Estudo sobre Ajustes nas Dimensões de Kernels Ajustes do lado do hardware Ajustes do lado do software Considerações Finais Metodologia Uso dos benchmarks Algoritmo Soma de Vetores - sumvector Algoritmo GEMM Algoritmo de Cálculo da Tabela de Senos e Cossenos sincos Desenvolvimento dos scripts para as execuções com o OpenTuner Realização dos Testes e Extração da Melhor Configuração Detecção de Padrões ou Tendências de Configuração

13 4.5 Considerações Finais Experimentos e Resultados Primeiros Resultados Experimento com execuções avulsas para o Soma de Vetores Experimento com execuções avulsas para o sincos Experimentos iniciais de automatização por força bruta Experimentos Experimento 1 com OpenTuner: Sincos Experimento 2 com OpenTuner: GEMM Experimento 3: Experimento força-bruta GEMM Experimento 4: Experimento força-bruta Sincos Resultados obtidos para Detecção e Tendências de Configuração Considerações Finais Conclusões Trabalhos Futuros Referências 95

14 Capítulo 1 Introdução Ao longo dos últimos anos houve uma grande evolução na área de Computação Paralela. Em específico, na computação paralela com elementos heterogêneos e na utilização de dispositivos aceleradores, como as Unidades de Processamento Gráfico (GPUs), Arranjos de Portas Programáveis (FPGAs) e arranjos de coprocessadores. Com a introdução de GPUs e do modelo de programação da plataforma de computação paralela CUDA, pela primeira vez, esses coprocessadores gráficos extremamente poderosos puderam ser usados por programadores C para trabalhos computacionalmente caros (COOK, 2013). Este trabalho tem como objetivo apresentar o contexto geral do desenvolvimento de aplicações em paralelo para dispositivos aceleradores. Um dos objetivos iniciais era mostrar as dificuldades no desenvolvimento de aplicações para GPUs quanto à escolha ou definição das dimensões do domínio do problema. Queríamos verificar a existência de um padrão na definição das configurações de execução que pudesse ser utilizado na escolha da melhor configuração, propondo uma solução para o problema de ajuste de kernels para dispositivos aceleradores, no caso Unidade de Processamento Gráfico (GPU) Considerações preliminares Microprocessadores que possuíam um único núcleo, os conhecidos processadores (single core) das famílias do Intel Pentium (GREER; HENRY, 1997) e AMD Opteron (CONWAY et al., 2010), impulsionaram o aumento de desempenho para uma escala de Bilhões de Operações em Ponto Flutuante por Segundo (GFLOPS). Esse contínuo avanço fez com os desenvolvedores de software acreditassem nos avanços do hardware para aumentar o desempenho de sua aplicação (KIRK; HWU, 2010). Contudo a partir de 2003, os microprocessadores atingiram uma limitação em seu desempenho. Questões relacionadas ao consumo de energia e dissipação de calor chegaram a 12

15 13 um estado crítico, que limitou fisicamente o aumento da frequência de clock, consequentemente essa limitação impactou no número de instruções que podiam ser executadas a cada ciclo de clock (KIRK; HWU, 2010). Essa limitação de velocidade de clock levou os fabricantes de microprocessadores aos projetos de arquiteturas com múltiplos núcleos, as arquiteturas multicore. Como uma tentativa de aumento do poder de processamento apostando no paralelismo entre tarefas. A partir desta mudança os desenvolvedores criaram o conceito de Revolução da Concorrência (SUTTER; LARUS, 2005), que é a utilização de várias threads que cooperam entre si e completam um determinado trabalho mais rapidamente. Atualmente, um outro conceito presente é o de Aceleração de Aplicações, no qual o código é preparado para fazer uso de novos recursos disponíveis nas plataformas, como instruções de vetorização (INTEL, 2015) ou para lançar a execução de algumas partes do código (kernels) em dispositivos aceleradores (offloading). Esses dispositivos aceleradores em conjunto com Unidades Centrais de Processamento (CPUs) multicore podem aumentar o desempenho das aplicações explorando o potencial paralelismo. Entre os aceleradores estão hardwares com arquiteturas reconfiguráveis, como os FPGAs, as GPUs (GASTER et al., 2011) e arranjos de coprocessadores (INTEL, 2013). As GPUs tem ganho destaque e tem se popularizado devido ao mercado de entretenimento e de dispositivos móveis. Uso de plataformas englobando CPU-GPU na última década elevaram o paradigma de programação paralela a um novo patamar (CHENG et al., 2014) Problema de Pesquisa Atualmente há uma lacuna entre o desempenho entre Unidade Central de Processamento (CPU) e GPU, que pode ser visto na Figura 1.1 para aplicações com paralelismo de dados. Esta grande diferença no desempenho tem despertado o interesse de pesquisadores que desenvolvem aplicações explorando o trabalho conjunto de CPUs e GPUs, dividindo a carga de trabalho entre o elemento principal de processamento CPU e lançando a execução de regiões de códigos paralelizáveis que podem ser transformadas em kernels para serem aceleradas em GPUs. Esse conceito já está sendo usado em aplicações de propósito geral e científico (KIRK; HWU, 2010). Para o lançamento da execução de um kernel em uma GPU, é necessário que o programador defina a configuração do arranjo de threads em termos das dimensões de grid, blocos e número de threads. A configuração determinará o número total de threads que serão executadas no hardware. Nesse contexto, o processo de desenvolvimento de software traz aos desenvolvedores uma série de desafios (GASTER et al., 2011) que buscam a obtenção de aplicações eficientes do ponto de vista de desempenho e consumo energético. Alguns desses desafios são:

16 14 Figura 1.1. Diferença de performance entre GPUs e CPUs. Fonte: (NVIDIA, 2017) Transformar código legado que aproveite novos recursos de sistemas multicore proporcionando uma modernização no código, ou paralelizando aplicações que extrapolam os recursos de CPUs para usarem dispositivos aceleradores. Estudos preliminares mostram que ferramentas que geram código acabam por fazer escolhas ingênuas ou genéricas. Os kits de desenvolvimento fornecidos pelos fabricantes dos dispositivos, ainda não estão acessíveis, de maneira amigável, para todas as classes de usuários. Existe a necessidade de aprendizado de um novo paradigma de desenvolvimento. Os kits abstraem o acesso aos recursos do hardware até certo nível, exigindo que o desenvolvedor declare explicitamente transferências de dados e dimensões do arranjo de threads que deve ser criado para a execução do kernel. O que muitas vezes leva a utilização de definições genéricas. Os desenvolvedores na maioria das vezes escolhem configurações padrões ou aleatórias que não utilizam todo o desempenho da arquitetura, levando muitas vezes a um desempenho inferior a um formato de aplicação sequencial. Neste cenário é evidente que deve-se criar mecanismos que ajuste as melhores configurações com base na arquitetura que está sendo utilizada Abordagem Proposta e Metodologia Para descobrir a melhor configuração, é necessário realizar testes executando um conjunto de benchmark com as configurações possíveis, sendo que com o número de configurações muito alto, um teste por força-bruta é descartado. Em um segundo momento optou-se pela utilização de uma ferramenta de autotuning como uma forma de realizar os testes dessas configurações de maneira mais eficiente. Esse

17 15 tipo de ferramenta realiza testes automatizados com auxílio de algoritmos de busca para encontrar a configuração ideal, com base em uma das métricas escolhidas. Este passo consiste na utilização de versões de kernels dos algoritmos na execução com o conjunto de configurações. Com base no resultado da métrica a ferramenta classifica e mantém a configuração que obteve o melhor resultado. O passo seguinte, após a melhor configuração ser encontrada, seria criar mecanismos que facilitassem o trabalho do desenvolvedor e que o permitissem codificar aplicações mais facilmente utilizando as configurações mais ajustadas para a arquitetura alvo, extraindo um melhor desempenho Objetivos O objetivo deste trabalho é analisar por meio de ferramentas de autotuning, qual a melhor maneira de extrair o melhor desempenho para dispositivos aceleradores GPUs, considerando a definição da configuração do arranjo de threads. Inicialmente, tinha-se como objetivo encontrar a melhor configuração e detectar padrões na escolha dessas configurações que impactassem na obtenção de melhorias de desempenho. E então, com estabelecer um mecanismo para facilitar a definição de configurações do arranjo de threads e consequentemente facilitar o desenvolvimento de aplicações heterogêneas Principais Contribuições No desenvolvimento desse trabalho foi necessário a criação de scripts para a ferramenta OpenTuner. Como a ferramenta permite ao usuário customizar a execução de testes, expondo uma interface e classes para o desenvolvedor. Desta forma, para cada um dos experimentos foi criado um script em Python. Cada um dos scripts executavam os benchmarks com as devidas configurações e coletavam o resultado para a métrica considerada no teste utilizando o programa de perfilamento nvprof da NVIDIA (NVIDIA, 2015a). Assim a principal contribuição deste trabalho é deixar um conjunto de scripts funcionais e apresentar uma metodologia aplicável de se encontrar configurações de kernels com auxílio da ferramenta OpenTuner Organização do Texto Neste Capítulo foi introduzido o problema de pesquisa, o contexto do trabalho, bem como seus objetivos e contribuições. Os trabalhos relacionados são apresentados no Capítulo 2. No Capítulo 3 é exposto o estudo sobre os ajustes nas dimensões de kernels e neste contexto os

18 16 ajustes que devem ser realizados do lado do hardware e software. No Capítulo 4 é mostrado como foi realizado o desenvolvimento da monografia e o Capítulo 5 apresenta os resultados obtidos com as execuções dos experimentos. Por fim no Capítulo 6 são apresentadas conclusões obtidas com base resultados gerados pelo Trabalho de Conclusão de Curso.

19 Capítulo 2 Referencial Teórico Este capítulo apresenta um referencial teórico a fim de embasar o desenvolvimento deste trabalho. A Seção 2.1 Modelos de programação paralela mostra os termos e classificações existentes para descrever os tipos de paralelismo e a seção 2.2 Dispositivos Aceleradores apresenta os principais dispositivos aceleradores utilizados atualmente. As seções 2.3 Modelo de Programação CUDA e 2.4 autotuning expõe respectivamente o modelo de programação paralela para GPUs e uma contextualização de como é o funcionamento das ferramentas de autotuning bem como a ferramenta OpenTuner Modelos de programação paralela No modelo de programação paralela há alguns termos que descrevem tanto o modelo de programação quanto a arquitetura paralela. O trabalho de Flynn (1972) foi um dos primeiros a criar um conjunto classificatório de arquiteturas existentes, seus termos são utilizados até hoje para descrever o tipo de paralelismo e a arquitetura: Single Instruction, Single Data (SISD): funcionamento característico do modelo de Von Neumann que corresponde um único fluxo de instruções opera sobre um único fluxo de dados, não há nenhum paralelismo; Single Instruction, Multiple Data (SIMD): corresponde ao processamento de vários dados sob o comando de apenas uma instrução. Multiple Instruction, Multiple Data (MIMD): múltiplas unidades executam fluxos independentes e separados de instruções em suas unidades de processamento. Há outras classificações que divide as máquinas segundo o compartilhamento de memória, e também outras classificações considerando o nível de abstração (STRINGHINI et al., 2012) 17

20 18 Multiprocessadores: máquinas com memória compartilhada que possuem velocidades de acesso uniforme e possibilitam o modelo de programação multithreaded. Single Instruction, Multiple Threads (SIMT): definição criada pela NVIDIA onde a computação SIMD é combinada com multitreading, cada thread em execução nos cores irão executar a mesma instrução como em um passo de execução Dispositivos Aceleradores Esta seção tem como principal objetivo demonstrar os principais dispositivos aceleradores, demonstrando semelhanças e diferenças em suas arquiteturas, será demonstrado FPGAs, Intel Xeon phi e GPUs. A seção sobre GPU terá maior destaque pois é o dispositivo que será utilizada neste trabalho Arranjo de Portas Programáveis (FPGA) Arranjos Programáveis são atualmente a base da área de computação reconfigurável, são dispositivos que permitem programar/reconfigurar o hardware do elemento de processamento conforme a necessidade (BRODTKORB et al., 2010). O primeiro dispositivo Arranjo de Portas Programáveis (FPGA) foi desenvolvido pelo co-fundador da empresa Xilinx Ross Freeman em Inicialmente foi desenvolvido para ser usado em lógica discreta, porém foi expandido para áreas como processamento de sinais, e recentemente para ser usado como dispositivo acelerador para computação de alta performance (BRODTKORB et al., 2010). Seu funcionamento consiste em um conjunto de blocos que podem ser configuráveis, bem como blocos de processamento de sinais digitais e opcionalmente conectados por uma interconexão flexível com cores CPUs. Quando configuradas, as FPGAs funcionam com os circuitos integrados feitos para aplicações específicas (BRODTKORB et al., 2010). A Figura 2.1 apresenta um esquema genérico de um sistema heterogêneo entre uma CPU e de um FPGA. O grande diferencial de um FPGA para um CPU é a capacidade de configurarmos seu hardware para desempenhar qualquer combinação de funções digitais que se deseja. Também pode-se implementar algoritmos de forma paralela, o que significa que pode-se processar uma enorme quantidade de dados de forma rápida e eficiente Intel Xeon Phi A Intel começou o desenvolvimento da tecnologia em 2004 quando as equipes de arquitetos de processadores começaram a procurar uma solução para reduzir o consumo de energia da família Intel Xeon que foi desenvolvida em 2001 (RAHMAN, 2013).

21 19 Figura 2.1. Sistema heterogêneo CPU e FPGA. Fonte: (BRODTKORB et al., 2010) Em primeiro momento as equipes de projeto se concentraram em núcleos Pentium 5 conectados através de uma interface de anel, adicionado a este formato unidades de função fixa. O objetivo do projeto era criar uma arquitetura que equilibrasse o multiprocessamento em nível de chip com threads e paralelismo em nível de dados. Intel Xeon Phi é composto por até 61 núcleos de arquitetura Intel. Para que haja uma comunicação entre tantos núcleos as interconexões são projetadas para manter o tráfego de memória/dados entre os núcleos e várias partes do chip. Na Figura 2.2 é demonstrado um layout lógico do co-processador Intel Xeon Phi, sendo separado em oito controladores de memória representados como "8x MC distribuídos", L2 cache totalmente coerentes entre si, essa coerência é feita através dos tag directories que são utilizados para procurar os dados de cache distribuídos entre os núcleos. Cada núcleo é capaz de executar operações vetoriais de 512 bits por ciclo e pode hospedar até 16 GB de memória de largura de banda. Para atingir um bom desempenho e eficiência energética, o código deve ter certas características: O fragmento do código que está sendo executado no coprocessador tem de ser altamente paralelo e deve ser escalável com o número de núcleos, resumindo, o fragmento do código deve ser capaz de utilizar todos os núcleos disponíveis sem mantê-los ociosos. Deve ser passível de transformações para utilizar instruções vetoriais. O código deve ser capaz de ocultar a latência de comunicação de Entrada/Saída com o host. Isso é necessário porque o processador do host será o elemento principal de processamento, sendo responsável pela maioria dos dados de entrada e saída para o armazenamento de dados permanente e para gerenciar o tráfego de rede para outros nós em um cluster ou grade. Atualmente o Intel Xeon Phi está sendo utilizado para aplicações de computação em áreas como, previsão do tempo, ciências médicas, exploração de energia, serviços financeiros e pesquisa acadêmica. Se tais aplicações citadas forem consideradas adequadas para a

22 20 Figura 2.2. Layout lógico do coprocessador Intel Xeon Phi. Fonte: (RAHMAN, 2013) arquitetura Xeon Phi e se estiverem programadas adequadamente, o Xeon Phi possibilita um desempenho e uma eficiência de energia muito maior do que o atingível somente pelos nós do host (RAHMAN, 2013) Unidades de Processamento Gráfico (GPU) GPUs são dispositivos aceleradores que atuam em conjunto com as CPUs, atualmente as duas principais fabricantes de GPUs são a NVIDIA e AMD (STRINGHINI et al., 2012). A primeira GPU foi criada em 1999 pela NVIDIA e denominada GeForce 256, possuía somente um processador gráfico de tempo real, também um vértice de ponto flutuante de 32 bits configuráveis e programáveis com APIs OpenGL e Microsoft DirectX (NICKOLLS; DALLY, 2010). Em 2006 a NVIDIA lançava a primeira arquitetura GPU programável em C com o CUDA, chamada de GeForce Seu hardware executava eficientemente 12,228 threads concorrentes em 128 cores. A GeForce 8800 foi a primeira GPU a usar threads escalares ao invés de processadores vetoriais, combinando linguagens escalares padrão com C e eliminando a necessidade de gerenciar registros e operações vetoriais. Foram adicionadas instruções para suportar C e outras linguagens de uso geral, incluindo aritmética de números inteiros, aritmética de ponto flutuante (IEEE 754) e instruções de acesso load/store com endereçamento de bytes (NICKOLLS; DALLY, 2010). O funcionamento de uma GPU é feita por centenas de núcleos (cores) que executam um determinado código através de centenas de threads concorrentes (STRINGHINI et al.,

23 ). Para abordar diferentes segmentos de mercado, a NVIDIA separou as GPUs em várias arquiteturas (Kepler, Fermi e Pascal), estas arquiteturas possuem diferenças, no número de multiprocessadores de fluxo para escalar o desempenho da computação, número de memórias para dimensionar a largura de banda e a capacidade da memória (NICKOLLS; DALLY, 2010). Nas próximas seções serão abordadas as diferenças entre as arquiteturas Fermi, Kepler e Pascal. Arquitetura Fermi A primeira GPU baseada em Fermi foi implementada com 3 bilhões de transistores, com 512 núcleos CUDA. Um núcleo CUDA executa uma instrução inteiro ou ponto-flutuante para cada thread em um ciclo de clock. Os 512 núcleos CUDA são organizados em 16 multiprocessor SM de 32 núcleos cada. A GPU tem seis partições de memória de 64 bits, para uma interface de memória de 384 bits, suportando no total 6 Giga bytes de memória. A conexão entre a GPU com a CPU é feita via barramento PCI-Express (NVIDIA, 2009). Na Figura 2.3 é demonstrado a divisão dos 16 SM, representadas pela tira retangular vertical que contém uma parte laranja (scheduler e dispath), porção verde (unidades de execução) e porções azuis claro que são arquivo de registro e cache L1. Figura 2.3. Modelo da arquitetura Fermi. Fonte: (NVIDIA, 2009)

24 22 Cada SM possui 32 cores CUDA, sendo que, cada processador CUDA possui uma unidade lógica aritmética (ALU) completamente pipeline e uma unidade de ponto flutuante (FPU) implementado sobre os padrões IEEE (NVIDIA, 2009). Figura 2.4. Fermi Streaming Multiprocessor (SM). Fonte: (NVIDIA, 2009) Na Figura 2.4 é apresentado como é a divisão de cada SM, sendo que, cada CUDA core implementa uma unidade lógica aritmética inteira e de ponto flutuante, e para cada SM são escalonados dois grupos de 32 threads paralelos chamados warps, 4 grupos de Unidades Funcionais Especiais (SFU) que são para instruções especiais (seno, cosseno e raiz quadrada) e 16 unidades de load/store. Arquitetura Kepler Projetado para ser uma potência de processamento paralelo junto com economia de energia, a arquitetura Kepler conta com 7, 1 bilhões de transístores e taxa de 1 Tera Flop de dupla precisão com uma eficiência de 80% superior a arquitetura Fermi e 3 maior o poder de desempenho por watt (NVIDIA, 2012). Também foram adicionados recursos como paralelismo dinâmico, desta forma, adiciona a capacidade para a GPU sincronizar os resultados e controlar o agendamento

25 23 de novos trabalhos sem envolver a CPU, permitindo que um kernel possa fazer chamadas a outros kernels. Hyper-Q que permite vários núcleos de CPU lancem trabalho em uma única GPU simultaneamente, aumentando a utilização da GPU e reduzindo o tempo de inatividade da CPU. Outra funcionalidade adicionada foi a unidade de gerenciamento de grids, este gerencia e prioriza os grids a serem executados na GPU e por fim a funcionalidade GPUDirect que permite que GPUs em um único computador ou em diferentes servidores possam trocar dados diretamente sem precisar envolver a CPU. Na Figura 2.5 mostra a arquitetura Kepler completa dividida entre os 15 SM com 192 núcleos cada. Figura 2.5. Diagrama completo arquitetura Kepler. Fonte: (NVIDIA, 2012) Cada uma das unidades de multiprocessadores da arquitetura Kepler SM possui 192 núcleos CUDA (Figura 2.6) de precisão única, e cada núcleo possui unidades lógica aritmética de inteiros e ponto-flutuante. A arquitetura mantém a aritmética de precisão simples e dupla do padrão IEEE e incluindo a operação de multiplicação de fusíveis (FMA). Para aumentar significativamente o desempenho de dupla precisão da GPU foi mantido as SFU porém com um acréscimo de 8x o número de SFU comparado com a arquitetura Fermi (NVIDIA, 2012).

26 24 Figura 2.6. Kepler Streaming Multiprocessor (SM). Fonte: (NVIDIA, 2012) Arquitetura Pascal Construído para ser o processador de computação paralela para atender as necessidades dos mercados de computação de alto desempenho. A arquitetura Pascal é composto de uma matriz de Graphics Processing Clusters (GPC), Texture Processing Clusters (TPC), multiprocessadores SM e controladores de memória. Uma placa de vídeo completa suporta seis GPCs, 60 Pascal SMs, 30 TPCs e oito controladores de memória de 512 bits (4096 no total) (NVIDIA, 2016). Cada GPC tem dez SMs. Cada SM tem 64 núcleos CUDA e quatro unidades de textura. Com 60 SMs tem um total de 3840 CUDA cores e 240 unidades de textura. Cada controlador de memória está ligado a 512 kilobytes de cache L2, e cada pilha é controlada por um par de controladores de memória. A GPU completa inclui um total de 4096 kilobytes de cache L2 (NVIDIA, 2016).

27 25 A Figura 2.7 mostra uma GPU completa com 60 unidades SM. Figura 2.7. Arquitetura Pascal completa com 60 unidades SM. Fonte: (NVIDIA, 2016) O SM da arquitetura Pascal incorpora 64 núcleos CUDA de precisão única, dividido em dois blocos de processamento, cada um com 32 núcleos CUDA de precisão única, um buffer de instrução, um warp scheduler e duas unidades dispatch. Comparando com a arquitetura Kepler, a arquitetura Pascal possui o mesmo número de registros, porém a nova arquitetura possui muito mais SM, significa que os threads em toda a GPU tem acesso a mais registros, e suporta mais threads, warps e blocos por threads. Outra comparação com arquitetura Kepler é que o SM de Pascal possui uma organização de dados mais simples e requer menos área de matriz e menos energia para gerenciar transferências de dados dentro do SM. O Pascal também fornece instruções de load/store sobrepostas para aumentar a utilização de pontos flutuantes, proporcionando maior desempenho e consumo de energia, cada warp scheduler é capaz de despachar duas instruções de warp por ciclo de clock Modelo de Programação (CUDA) CUDA é a tecnologia da NVIDIA introduzida em 2006 conhecida como Arquitetura de Computação Paralela de Propósito Geral (STRINGHINI et al., 2012). Usando CUDA você pode acessar a GPU para resolver problemas computacionais complexos de uma forma eficiente (CHENG et al., 2014).

28 26 Figura 2.8. Pascal unidade SM. Fonte: (NVIDIA, 2016) A plataforma CUDA é acessível através das diretrizes de compilação, interfaces de programação e extensões para linguagens de programação padrão da indústria (CHENG et al., 2014), incluindo C, C++, Fortran e Python. Para realizar códigos paralelos, desenvolvedores devem ter um conhecimento aos recursos arquiteturais da GPU e conhecimento básico de CPU, principalmente o conceito de localidade. Existem dois tipos básicos de localidade. Localidade temporal que refere-se a reutilização de dados e recursos dentro de períodos de tempo relativamente pequeno, e localidade espacial que refere-se ao uso de elementos de dados em locais de armazenamento relativamente próximos (CHENG et al., 2014). Outro conhecimento que o desenvolvedor deve ter sobre códigos paralelos são de host que é a CPU e sua memória e device que é a GPU e sua memória, contudo, o componente principal do modelo de programação CUDA é o kernel. O kernel é o código que deve ser executado no dispositivo GPU. Quando um kernel é lançado o controle é retornado imediatamente para o host liberando a CPU para executar tarefas adicionais complementadas pelo código paralelo executado no dispositivo. Na Figura 2.9, o código serial é executado no host, enquanto o código paralelo é executado no dispositivo GPU (CHENG et al., 2014). Um fluxo de processamento de um programa CUDA segue o

29 27 padrão: 1. Copie os dados da memória CPU para a memória da GPU; 2. Realiza as chamadas do kernels para operar sobre os dados armazenados na memória da GPU; 3. Copie dados de volta da memória GPU para a memória da CPU. Figura 2.9. Fluxo de execução de um código em CUDA. Fonte: (CHENG et al., 2014) Quando uma função kernel é iniciada do lado do host, a execução é movida para um dispositivo onde um grande número de threads é gerado e cada segmento executa as instruções especificadas pela função do kernel (CHENG et al., 2014). CUDA expõe uma abstração de hierarquia de thread demonstrado na Figura 2.10, decomposta em blocos de threads e grids de blocos. Todas as threads gerados por um único lançamento do kernel são coletivamente chamados de grid. Todos os segmentos em um grid compartilham o mesmo espaço de memória global e é composto de muitos blocos de threads. Um bloco de thread é um grupo de threads que podem cooperar entre si por meio de memória compartilhada local e sincronização local (CHENG et al., 2014).

30 28 Figura Divisão hierárquica de grids, blocos e threads. Fonte: (CHENG et al., 2014) Vale ressaltar que os threads de blocos diferentes não podem cooperar e cada segmento de thread depende das coordenadas blockidx (índice do bloco dentro de uma grid) e threadidx (índice de threads dentro de um bloco) para distinguirem uma das outras (CHENG et al., 2014). Esses valores são disponibilizados em variáveis embutidas e podem ser acessados dentro do código do kernel para o cálculo do id global de cada uma das threads criadas com o lançamento da execução do kernel. CUDA permite definir um arranjo de threads formado por um grid de blocos, em até três dimensões, e cada bloco também pode ter até três dimensões. Desta forma, problemas com domínios de dimensões variadas pode ser solucionados com um mapeamento em um grid de blocos de threads, tal como uma matriz ou uma imagem bidimensional (STRINGHINI et al., 2012). Uma configuração de grid(gx, gy, gz) e bloco(bx, by, bz) cria a estrutura do arranjo de threads. A Figura 2.11 apresenta a correspondência da estrutura criada na visão lógica (software) e o seu mapeamento para o hardware. A Figura 2.12 apresenta como os blocos são atribuídos para os multiprocessadores e suas threads são organizadas em warps que são escalonados e as threads pertencentes a um warp (lanes) serão executadas por cada core. Por exemplo, em uma soma de vetores temos que dois arranjos A e B de tamanho N tem seus elementos somados um a um e consequentemente o resultado da adição é armazenado em um vetor resultante, aqui denominado C. O Código 2.1 apresenta como a soma de dois vetores pode ser feita na linguagem C.

31 29 Figura Mapeamento da visão lógica para o hardware Figura Organização do arranjo de threads 1 int N = 1024; 2 int sumvector ( f loat A, f loat B, float C) { 3 int i ; 4 for ( i =0; i <N; i ++){ 5 C[ i ] = A[ i ] + B[ i ] ; 6 } 7 } Código 2.1. Exemplo de soma de vetores na linguagem C Contudo, para o desenvolvimento do código em CUDA a função sumvector deve ser modificada para se transformar em um kernel. Como há paralelismo entre as iterações do laço, pode-se escrever uma versão para CUDA substituindo as N iterações por um arranjo de threads no qual cada thread executaria o código equivalente a uma das iterações do laço

32 30 original. No intuito de demonstrar a complexidade da definição do arranjo de threads os Códigos 2.2, 2.3 e 2.4 apresentam variações de algums definições possíveis. Existem diversas maneiras de se especificar o arranjo de threads que irão executar o código do kernel. O Código 2.2 apresenta a possibilidade de executar o kernel criando um grid com N blocos com 1 thread cada (linha 10). A extensão que CUDA faz da linguagem C modifica a chamada de funções para que a ativação de uma função kernel seja distinta da chamada de uma função comum, a chamada V ecadd <<< N, 1 >>> (d_a, d_b, d_c, N) traz a definição das dimensões do grid e do bloco de threads entre <<< e >>> que antecedem os parâmetros da função kernel. 1 global void VecAdd( f loat A, float B, float C, int N) { 2 int id = blockidx. x ; 3 i f ( id < N) 4 C[ id ] = A[ id ] + B[ id ] ; 5 } 6 7 int main ( ) { 8 / Declaracao e t r a n s f e r e n c i a s de dados foram suprimidas. / 9 int N = 1024; 10 VecAdd<<<N, 1>>>(d_A, d_b, d_c, N) ; return 0 ; 13 } Código 2.2. Exemplo de soma de vetores em CUDA utilizando N blocos com 1 thread cada No Código 2.3 apresenta uma outra versão, a possibilidade de se criar um grid com um único bloco de N threads. 1 global void VecAdd( f loat A, float B, float C, int N) { 2 int id = threadidx. x ; 3 i f ( id < N) 4 C[ id ] = A[ id ] + B[ id ] ; 5 } 6 7 int main ( ) { 8 / Declaracao e t r a n s f e r e n c i a s de dados foram suprimidas. / 9 VecAdd<<<1, N>>>(d_A, d_b, d_c, N) ; return 0 ; 12 } Código 2.3. Exemplo de soma de vetores em CUDA utilizando 1 bloco com N threads

33 31 O Código 2.4 apresenta uma combinação do uso de blocos e threads, o que possibilita uma definição de arranjos de threads mais elaborados, o que é útil para problemas com estruturas de domínio mais complexas. 1 global void VecAdd( f loat A, float B, float C, int N) 2 { 3 int id = blockdim. x blockidx. x + threadidx. x ; 4 i f ( id < N) 5 C[ id ] = A[ id ] + B[ id ] ; 6 } 7 8 int main ( ) { 9 / Declaracao e t r a n s f e r e n c i a s de dados foram suprimidas. / int threadsperblock = 256; 12 int blockspergrid = (N + threadsperblock 1) / threadsperblock ; 13 VecAdd<<<blocksPerGrid, threadsperblock >>>(d_a, d_b, d_c, N) ; return 0 ; 16 } Código 2.4. Exemplo de soma de vetores em CUDA com um arranjo de threads definindo-se um grid de blocos de threads Embora foi utilizado nos exemplos definições únicas para as dimensões de grids e blocos, essas dimensões podem ser declaradas com o tipo dim3 possibilitando definições de arranjos mais complexos. O Código 2.5 apresenta uma declaração de um grid utilizando as dimensões x e y com o valor 32 e um bloco utilizando a dimensão x também com o valor 32. Esta definição irá criar um grid de = 1024 blocos e cada bloco terá 32 threads, o que permitiria no nosso exemplo de soma de vetores o cálculo para vetores de elementos. 1 int main ( ) { 2 / Declaracao e t r a n s f e r e n c i a s de dados foram suprimidas. / 3 4 dim3 g r i d ( 3 2, 3 2, 1 ) ; 5 dim3 bloco ( 3 2, 1, 1 ) ; 6 VecAdd<<<grid, bloco >>>(d_a, d_b, d_c, N) ; 7 8 return 0 ; 9 } Código 2.5. Exemplo de soma de vetores em CUDA com um arranjo de threads definindo-se um grid de blocos de threads O cálculo do id global de cada thread depende das dimensões para grid e blocos utilizada. Nos Códigos 2.2, 2.3 e 2.4 é visto que existem diversas maneiras de se definir o

34 32 arranjo de threads que executarão o código da função kernel e o id global da thread deve ser calculado conforme a estrutura do arranjo de threads declarado. O cálculo do id pode ser extraído para funções que serão ativadas conforme a definição do grid e dos blocos. No Código 2.6 são apresentadas funções para o cálculo do id das threads quando são utilizadas apenas uma das dimensões (1D_1D), uma versão para grids que utilizem duas dimensões e blocos com uma dimensão (2D_1D) e a versão mais genérica para quando as 6 dimensões são utilizadas (3D_3D). 1 device int getglobalidx_1d_1d ( ) { 2 int threadid = blockidx. x blockdim. x + threadidx. x ; 3 return threadid ; 4 } device int getglobalidx_2d_1d ( ) { 7 int blockid = blockidx. y griddim. x + blockidx. x ; 8 int threadid = blockid blockdim. x + threadidx. x ; 9 return threadid ; 10 } device int getglobalidx_3d_3d ( ) { 13 int blockid = blockidx. x + blockidx. y griddim. x + griddim. x 14 griddim. y blockidx. z ; 15 int threadid = blockid ( blockdim. x blockdim. y blockdim. z ) 16 + ( threadidx. z ( blockdim. x blockdim. y ) ) 17 + ( threadidx. y blockdim. x ) + threadidx. x ; 18 return threadid ; 19 } Código 2.6. Exemplo da linearização das dimensões em um id único Assim, para um grid de blocos de threads a linearização do id global de cada uma das threads pode ser calculada com operações de multiplicação e adição utilizando as variáveis embutidas (blockidx.x, blockdim.[x,y], threadidx.[x,y,z]). O Código 2.7 demonstra a implementação do kernel em CUDA. 1 global void vecadd ( f loat A, float B, float C, int N, int funcid ) { 2 int id = getglobalidfunc [ funcid ] ( ) ; 3 i f ( id < N) 4 C[ id ] = A[ id ] + B[ id ] ; 5 } Código 2.7. Exemplo do kernel para a soma de vetor A verificação se o id da thread é menor que o número de elementos na estrutura de dados é necessária para que apenas threads no domínio sejam executadas. Então, da mesma

35 33 forma com as dimensões separadas, é criado o kernel que fará a execução em paralelo. No repositório do GitHub 1 é apresentado o código completo do exemplo soma de vetores em CUDA. Quando os kernels são gerados é necessário que haja uma compilação do código do kernel escrito na linguagem C para a representação de código intermediário das GPUs da NVIDIA que é denominado de Parallel Thread Execution PTX (NVIDIA, 2014). O nvcc é um compilador para código C/C++ com suporte a CUDA que fornece opções de linha de comando simples semelhantes a compiladores como o GCC. Seu fluxo de trabalho consiste em separar o código que será executado do lado host e o código do kernel que executará na GPU. Normalmente, para o código do lado host utiliza o compilador padrão do sistema e gera o código para dispositivo em um formato de montagem intermediário chamado de PTX (NVIDIA, 2014). A partir do código PTX o código binário final para o conjunto de instruções da arquitetura do dispositivo é gerado e pode ser executado. Contudo, a escrita do código em CUDA e sua execução em GPU não fornece análise de desempenho do programa, e pode ser que código desenvolvido não esteja utilizando todos os recursos e como consequência não atinja um desempenho satisfatório na arquitetura em questão (CHENG et al., 2014). Para esta análise, há uma ferramenta de perfilamento integrada com CUDA que é utilizada para analisar o desempenho do programa, esta ferramenta é chamada de NVIDIA Profiler nvprof (CHENG et al., 2014). O nvprof foi introduzido na versão 5 do CUDA e tem a função de exibir informações detalhadas na linha de comando. Ele permite a coleta da linha do tempo de atividades relacionadas ao CUDA tanto na CPU quanto na GPU, incluindo a execução do kernel, transferências de memória, coletando contadores do hardware, eventos e métricas de desempenho para os kernels CUDA (CHENG et al., 2014). Para identificar gargalos de desempenho em um kernel, é importante escolher métricas de desempenho apropriadas e realizar comparações, sendo que há três limitadores para o desempenho de um kernel. Estes limitadores são a largura de banda da memória (métrica gld_efficiency), utilização dos recursos (métrica achieved_occupancy), número de instruções executadas (métrica inst_executed) e latência de memória (sm_efficiency) (CHENG et al., 2014). A utilização do nvprof com estas métricas é importante para a programação de CUDA pelas seguintes razões (CHENG et al., 2014): Uma implementação de kernel simples geralmente não produz o melhor desempenho. As métricas pode ajudar a encontrar regiões críticas de código que são gargalos de desempenho. 1 < sumvector.cu>

36 34 CUDA particiona os recursos de computação em SM com vários blocos de threads. Esse particionamento faz com que alguns recursos se tornem limitadores de desempenho, com as métricas pode-se obter informações sobre como os recursos de computação estão sendo utilizados. CUDA fornece uma abstração da arquitetura de hardware que permite controlar a concorrência entre threads. As métricas podem ajudar a medir, visualizar e orientar as otimizações, pois fornecem uma visão profunda do desempenho do kernel e ajuda a identificar estrangulamentos nos kernels Autotuning Autotuning é uma técnica de otimização de software concebida a partir de bibliotecas numéricas sequenciais, em especial da biblioteca Automatically Tunned Linear Algebra Software (ATLAS). Whaley e Dongarra (1999) utilizaram o conceito de auto-tunner, pois perceberam que percorrer todo o espaço de busca para a geração automática e otimização de uma hierarquia de memória não seria uma tarefa simples e rápida, então proporam a idéia de uma aplicação que realizasse essa busca de maneira automatizada. Assim Williams (2008) propôs em sua tese o conceito de auto-tunner que tem a tarefa de buscar, dentro de um espaço de possibilidades, a implementação de uma rotina kernel que apresente o melhor desempenho para uma determinada arquitetura. Segundo Williams (2008), um autotuning deve ser criado a partir de três conceitos principais, espaço de soluções, geração de código e exploração do espaço de otimização, este conceito foi implementado na ferramenta OpenTuner. O espaço de soluções refere-se a explorar o melhor desempenho do kernel. Esta exploração utiliza o conjunto de parâmetros que o kernel necessita para realizar esta busca, estes parâmetros podem ser estruturas de dados alternativos para o algoritmo, eliminação de desvios condicionais e otimizações de laços. A partir dos parâmetros determinados pelo espaço de otimização, o auto-tunner gera mutações de kernel em uma linguagem de alto nível. A escolha de qual mutação apresenta o melhor desempenho utilizando a exploração do espaço de otimização. A exploração do espaço de otimização pode ser feita a partir de três abordagens. A primeira é através da busca exaustiva, a qual o kernel passa por todas as possibilidades que a arquitetura oferece, nesta abordagem o espaço de busca pode-se tornar inviável em termos de tempo. A segunda abordagem é o uso de heurísticas baseadas na arquitetura, estas heurísticas pode ser o intervalo de valores que uma arquitetura delimita, assim o autotuning trabalha somente dentro deste espaço. Uma terceira técnica que é utilização de algoritmos de busca, desta forma, o auto-tuner efetua uma busca linear dentro de todo o espaço de busca de um determinado parâmetro.

37 35 Na ferramenta OpenTuner que será apresentado na Seção utilizou três tipos de algoritmos de busca, bem como implementou a ferramenta utilizando os conceitos apresentados OpenTuner OpenTuner é uma ferramenta de código aberto que tem como objetivo a fácil construção de programa auto-tuner para um domínio específico (ANSEL et al., 2014). Apresenta uma configuração extensível e representação técnica capaz de suportar tipos de dados complexos e definidos pelo usuário e heurísticas de pesquisa personalizadas. Seu conceito central é encontrar o melhor desempenho para que a aplicação necessita com o menor tempo de trabalho, para isso, utiliza técnicas de busca que intercalam entre si, sendo que as técnicas que retornam valores abaixo do esperado são descartados do que técnicas que encontraram valores esperados. Na Figura 2.13 mostra como o OpenTuner realiza o processo de autotuning. Figura Funcionamento do OpenTuner. Fonte: (ANSEL et al., 2014) Primeiro deve ser especificado o espaço de busca, este espaço inclui um conjunto de parâmetros que o OpenTuner pesquisara para realizar os testes. Segundo, o usuário deve implementar o programa autotuning que realizará os testes no conjunto de parâmetros utilizando as técnicas de busca (o funcionamento das técnicas é demonstrado na Figura 2.14), esta implementação do usuário deve ser realizado na linguagem de programação Python e deve haver o método configuration especificado pela Interface de Programação de Aplicativos (API) do OpenTuner que realizará as escolhas dos parâmetros, este método retorna os parâmetros (measurement) que será utilizado no método run que será executado e após o término da execução retornará as medidas de tempo, métricas ou consumo energético confome definição do usuário. Na execução do método de configuration é realizado a escolha da técnica de busca que será executado naquele momento, sendo que na primeira execução todos os algoritmos de busca realizam a execução e é retornado uma taxa utilizável para comparar com os outros algoritmos. Assim que realizado a primeira execução todos os algoritmos estão com uma taxa

38 36 Figura Modelo de divisão de trabalho das técnicas de busca. Fonte: (ANSEL et al., 2014) armazenada e compartilhada no ResultsDB, a partir destas taxas o algoritmo AUC Bandit encapsula um trade-off fundamental chamado exploitation (uso da técnica mais conhecida) e exploitation (estimativa de desempenho de todas as técnicas) e realiza novamente os testes com os algoritmos. A partir de várias execuções o AUC bandit terá um conjunto de resultados entre as técnicas que tiveram um valor satisfatório (estes serão ativados novamente) e com técnicas que não tiveram um valor satisfatório (não são executados novamente). A ferramenta OpenTuner utiliza de três algoritmos de busca Differential Evolution, Particle Swarm Optimization e Torczon Hill Climber de um algoritmo de moderação denominado AUC Bandit. Differential Evolution: é um método de busca direta e paralelo que utiliza vetores de parâmetros NP D-dimensionais com uma população G. A população inicial é escolhida aleatoriamente e deve tentar cobrir o espaço do parâmetro uniformemente, basicamente, o Differential Evolution gera novos vetores de parâmetros adicionados a diferença ponderada entre dois vetores populacionais a um terceiro vetor. Se o vetor resultante produz um valor de função objetivo inferior ao de um elemento da população predeterminado, desta forma o vetor novo substitui o vetor com o qual foi comparado, caso contrário o vetor antigo é armazenado (STORN; PRICE, 1997). Na Figura 2.15 mostra em duas dimensões os diferentes vetores gerados para a busca, sendo que, o primeiro vetor é demonstrado pela "onda"de maior tamanho e o mínimo é onde se encontra a solução ótima. Particle Swarm Optimization: Desenvolvido em 1995 por Kennedy e Eberhart o

39 37 Figura Processo de geração da função objetiva. Fonte: (STORN, 1996) Particle Swarm Optmization pode ser entendido pela ideia de um enxame de abelhas. Imagine um enxame de abelhas em um campo, o objetivo é encontrar no campo a localização com a maior densidade de flores sem qualquer conhecimento do campo a priori. Primeiro as abelhas começam em locais aleatórios com velocidades aleatórias à procura de flores. Cada abelha pode recordar dos locais que encontrou a maioria das flores, também sabe de algum modo os locais onde as outras abelhas encontraram uma abundância de flores. Dividida entre o retorno ao local onde pessoalmente encontrara a maioria das flores, ou explorando a localização relatada por outras abelhas, alterando sempre sua trajetória de voar entre sua escolha e a escolha das outras abelhas, desta forma as abelhas exploram o campo, sempre sobrevoando aos locais onde tem maior concentração de flores, logo todas as abelhas enxameiam em torno deste ponto (KENNEDY; EBERHART, 1995). O funcionamento do algoritmo de busca consiste primeiro de uma partícula que tem como princípio buscar a melhor localização pessoal bem como a melhor localização geral (local onde as outras abelhas foram), segundo a posição em que a "abelha"está localizada, por exemplo uma coordenada do plano x, y, terceiro uma função de aptidão que retorna a densidade de flores, quanto maior a densidade melhor a localização. Também para compor o algoritmo há a função pbest que retorna a melhor localização de flores para uma partícula e a função gbest que é a melhor localização de flores segundo todas as abelhas (ROBINSON; RAHMAT-SAMII, 2004). Na Figura 2.16 mostra que as partículas individuais (1 e 2) são aceleradas em direção a localização da melhor solução gbest e a localização do seu próprio melhor pbest. Torczon Hill Climber: O algoritmo de busca Hill Climber é um algoritmo iterativo que começa através de uma possível solução, em seguida, tenta encontrar uma solução

40 38 Figura Funcionamento do algoritmo de busca Particle Swarm Optimization, em um espaço de busca 2D. Fonte: (ROBINSON; RAHMAT-SAMII, 2004) melhor, alterando gradativamente um único elemento da solução. Se a mudança produz uma solução melhor, uma alteração incremental é feita para a nova solução, repetindo até não encontre mais melhorias. Este algoritmo lembra a ideia de uma escalada a uma montanha (RUSSELL; NORVIG, 2003). Hill Climbing atinge soluções ótimas para problemas convexos, para outros casos apresenta somente máximos locais (pico que é maior do cada um dos seus estados vizinhos, mas inferior ao máximo global), cumes que são uma sequência de máximos locais que é muito difícil para algoritmos navegarem, e por planos, do qual não existe saída para cima, ou um cume a partir da qual o progresso é possível. Para conter esses problemas de busca, há algoritmos variantes do Hill Climbing como Stochastic Hill Climbing que escolhe aleatoriamente entre os movimentos ascedentes, a probabilidade da escolha do próximo passo varia com a inclinação do movimento de subida. Geralmente a convergencia deste algoritmo é mais lenta do que o Hill Climbing, porém encontra soluções melhores. Também há o First-Choice Hill Climbing que primeiro implementa o Stochastic Hill Climbing, gerando sucessores aleatoriamente até que seja gerado um melhor que o estado atual. Esta estratégia é boa para casos onde há milhares de estados que podem ser sucessores (RUSSELL; NORVIG, 2003). Na Figura 2.17, demonstra o algoritmo de busca Hill Climbing em seu formato mais básico. Em cada passo o nó atual é substituído pelo melhor vizinho, isso significa que o vizinho com o mais alto VALUE, este passo leva a possíveis máximos locais.

41 39 Figura Demonstração do Algoritmo de busca Hill Climbing. Fonte: (RUSSELL; NORVIG, 2003) 2.5. Considerações Finais Neste capítulo foram apresentados alguns conceitos e trabalhos relacionados com nossa proposta. Enfatizou-se principalmente a definição das dimensões do arranjo de threads para o lançamento da execução de kernels que a plataforma CUDA deixa a critério do desenvolvedor, para que seja feita uma definição explícita conforme o domínio do problema. Grande parte das ferramentas de compilação apresentadas que geram código para GPU utilizam-se de definições genéricas para as dimensões (3D_3D) para garantir a cobertura no cálculo do id global das threads, mesmo para kernels que não utilizam todas as dimensões. O que no mínimo colocará mais instruções no código do kernel para esse cálculo. Foi apresentado também o OpenTuner uma ferramenta para autotuning que pretendese utilizar para a exploração do espaço de configurações das dimensões de grids e blocos em busca de padrões que possibilitem o melhor ajuste entre código e características do hardware.

42 Capítulo 3 Estudo sobre Ajustes nas Dimensões de Kernels No contexto de GPUs, existe uma relação entre a visão do modelo de programação que se tem no software com o modelo de execução no hardware. No Capítulo 2 foi apresentado o embasamento teórico levantado para o desenvolvimento deste trabalho. A Seção apresentou alguns conceitos sobre GPUs e a Seção 2.3 introduziu o desenvolvimento de aplicações paralelas para GPU utilizando CUDA. Os recursos e capacidades do hardware determinam se os kernels de uma aplicação podem ser lançados para a execução ou não. Uma das principais restrições é a quantidade total de memória que se tem disponível em cada dispositivo, pois as aplicações alocam dados durante sua execução e se a memória do dispositivo não for suficiente para alocar todos os dados necessários, tem-se um problema de capacidade de armazenamento que deverá ser resolvido com alguma espécie de particionamento dos dados. Outras restrições importantes são as dimensões máximas que são impostas pelo modelo da arquitetura para grids e blocos. Uma configuração de grid(x, y, z) e bloco(x, y, z) cria a estrutura de um arranjo de threads. No caso específico de GPUs, a responsabilidade de especificar as dimensões do arranjo de threads que deve ser criado, fica sobre o desenvolvedor. Algumas abordagens tentam utilizar ferramentas de compilação que geram código de maneira automática para GPU, entre elas estão o PoLLy (GROSSER et al., 2011, 2012), o KernelGen (MIKUSHIN et al., 2013, 2014) e o PPCG (VERDOOLAEGE et al., 2013). Essas ferramentas geram código para um determinado alvo (CPU ou GPU), no caso de GPU muitas vezes é gerado um código genérico, utilizam dimensões genéricas para o arranjo de threads (grid, bloco, threads) que será criado, não se preocupam com o ajuste às características arquiteturais. No contexto dessas ferramentas a preocupação está no fato de ser ou não ser possível gerar uma versão de código que possa ser executado em paralelo. E então, uma versão paralela é gerada com base na experiência dos desenvolvedores ou de maneira genérica sem a 40

43 41 preocupação se este possui um ajuste melhor às características arquiteturais. Ajustar o código dos kernels para a arquitetura que está sendo utilizada é essencial para se garantir uma utilização eficiente dos recursos disponíveis. Entretanto, não é uma tarefa trivial, primeiro deve-se conhecer os componentes arquiteturais da GPU e ajustar o código para que extraia o melhor desempenho possível. Assim, o tema deste trabalho está relacionado com o ajuste de código com base nas características arquiteturais. A busca no conjunto de configurações possíveis e válidas para o arranjo de threads, subdividido em grids, blocos e threads é guiada pelos resultados obtidos de métricas coletadas diretamente do hardware. O objetivo é escolher a melhor configuração que favoreça ou propicie o melhor desempenho da GPU de maneira global ou em determinados componentes, em específico Ajustes do lado do hardware Desta forma, irá se verificar a escolha de uma determinada configuração ou a priorização de uma das dimensões influencia no desempenho. Uma vez que para diferentes configurações as podem apresentar diferenças no desempenho. No referencial teórico é visto que a execução de um kernel é necessário a definição das dimensões de um arranjos de threads com grid(x,y,z) e bloco(x,y,z). Para realizar a escolha de configurações, o desenvolvedor deve respeitar certas condições e restrições pré determinadas pelo hardware para os grids e blocos, sendo elas: 1. O total de threads criadas em uma configuração será gx gy gz bx by bz. 2. A multiplicação das dimensões dos blocos não pode ultrapassar 512 ou 1024 dependendo da arquitetura. Este valor é o número máximo de threads por blocos (bx by bz 1024). 3. A multiplicação das dimensões do bloco (bx by bz) tem que ser divisível por 32, pois as threads são organizadas durante a execução em grupos de 32, em warps. Qualquer configuração que não seja múltipla de 32 apresentará o problema de divergência na execução do kernel, no qual ficam lanes inativos dentro do warp, o que degrada o desempenho. 4. O total de threads criadas em uma configuração deve ser menor que o tamanho de N, esta condição se dá pelo fato que as threads não podem realizar trabalho com espaços de memórias não alocados. Para exemplificar o número de configurações válidas que pode haver para um determinado algoritmo, a Tabela 3.1 descreve o número de possibilidades que pode haver para o algoritmo com um laço de N iterações mapeadas em threads, como no exemplo soma de vetores. Ainda na Tabela 3.1 é possível ver que a escolha da melhor configuração se torna uma tarefa não trivial. Por exemplo, para o número de iterações igual a 64 pode-se haver

44 42 Tabela 3.1. Configurações válidas para um determinado número de iterações Tamanho do vetor Configurações Válidas as seguintes configurações de grids (na Tabela 3.2 apresentado por gx,gy e gz) e blocos (na Tabela 3.2 apresentado por bx, by e bz). Tabela 3.2. Configurações válidas para o número de iterações N = 64 (gx,gy,gz)(bx,by,bz) (1, 1, 2),(32, 1, 1) (1, 2, 1),(32, 1, 1) (2, 1, 1),(32, 1, 1)... (1, 1, 2),(8, 4, 1) (1, 2, 1),(8, 4, 1) (2, 1, 1),(8, 4, 1)... (1, 1, 2),(1, 1, 32) (1, 2, 1),(1, 1, 32) (2, 1, 1),(1, 1, 32) A escolha da melhor configuração que se ajusta às características do hardware tornase um problema. E para solucioná-lo é necessário testar todas as possíveis configurações utilizando a técnica de força bruta ou alguma ferramenta que explore o espaço de configurações utilizando técnicas de acelerem a busca e elimine testes desnecessários com configurações que não melhoram o desempenho, ferramentas de autotuning. Seja por força bruta, seja por autotuning, a escolha das melhores configurações se

45 43 dará pela coleta de métricas de desempenho que a plataforma CUDA disponibiliza e podem ser coletas com a ferramenta nvprof. Algumas dessas métricas são: 1. sm_efficiency: Porcentagem de tempo que uma warp está ativa em um multiprocessador em relação a todos os multiprocessadores na GPU. 2. achieved_occupancy: Relação média entre warps ativos com o número máximo de warps suportadas no multiprocessador. 3. inst_executed: Número de instruções executadas 4. gld_efficiency: Relação do throughput carga de memória global solicitada para o throughput de carga global requerido. Queremos com os testes via ferramentas encontrar a melhor configuração ou padrões de configurações que possibilitem o melhor ajuste do código de kernels nas dimensões e características dos dispositivos. Com estas informações queremos verificar se é possível criar uma heurística com os padrões detectados pelas ferramentas de autotuning, que será utilizada para próximos trabalhos criar um passo para a ferramenta Low Level Virtual Machine (LLVM )-PoLLy. A ideia é que dado um código com laços aninhados, verificações se ele é paralelizável sejam feitas e se for paralelizável ele terá seu código transformado em uma versão paralela e um kernel de código ajustado será gerado conforme o padrão detectado Ajustes do lado do software Laços de repetição são alvo de ferramentas que trabalham com o modelo polyhedral (BENABDERRAHMANE et al., 2010), pois em sua maioria códigos que possuem laços apresentam algum paralelismo que em uma execução sequencial ficaria escondido. Laços aninhados são um problema para desenvolvimento de códigos paralelos se apresentam dependências entre as iterações. Normalmente, as ferramentas aplicam transformações modificando o domínio de iterações formado pelas dimensões criadas com os índices do aninhamento. Para que seja possível a criação de um kernel para GPU é necessário que o laço ou um conjunto de laços aninhados tenham iterações que possam ser executadas em paralelo, pois o modelo de execução das GPU trabalha com paralelismo de dados. O Código 3.1 mostra um exemplo de um algoritmo que calcula uma tabela de senos e cossenos, note que o statement (S_1) que pertence ao corpo do laço mais interno é totalmente livre de dependências pois não há acessos a elementos entre iterações. Dado um índice os elementos dos arranjos são acessados e o cálculo pode ser feito.

46 44 1 for ( i = 0 ; i < nx ; ++i ) { 2 for ( j = 0 ; j < ny ; ++j ) { 3 for ( k = 0 ; k < nz ; ++k ) { 4 i n d i c e = ( i ny nz ) + ( j nz ) + k ; 5 xy [ i n d i c e ] = s i n ( x [ i n d i c e ] ) + cos ( y [ i n d i c e ] ) ; // S_1 6 } 7 } 8 } Código 3.1. Exemplo de algoritmo que possui laços aninhados. Além do ajuste nas dimensões de grids e blocos de threads que é do lado do hardware. O ajuste que queremos testar nos códigos dos benchmarks também trabalha sobre o software. O Código 3.1 pode ser transformado em Dadas as dimensões que possibilitam a criação do arranjo de threads organizado em um grid de blocos de threads e dado um código com laços aninhados. A ideia é migrar iterações de laços para o arranjo de threads. O Código 3.2 apresenta parte do código de um benchmark com 3 laços aninhados, os testes que queremos realizar neste código é criar algumas versões de kernels que executem o código original com 3 laços, outra que execute o código com os 2 laços mais internos, outra que execute apenas o laço mais interno e finalmente um kernel que execute apenas as instruções que estão no corpo do laço mais interno. 1 global void sincos_kernel_3 (DATA_TYPE x, DATA_TYPE y, DATA_TYPE xy, int nx, int ny, int nz ) { 2 int i, j, k, i n d i c e ; 3 for ( i = 0 ; i < nx ; ++i ) { 4 for ( j = 0 ; j < ny ; ++j ) { 5 for ( k = 0 ; k < nz ; ++k ) { 6 i n d i c e = ( i ny nz ) + ( j nz ) + k ; 7 xy [ i n d i c e ] = s i n ( x [ i n d i c e ] ) + cos ( y [ i n d i c e ] ) ; 8 } 9 } 10 } 11 } Código 3.2. Código do kernel gerado com os 3 laços O índice aqui é calculado pelos laços, uma cópia do kernel executando os três laços. E a ativação do kernel se dará pela chamada sincos_kernel_3 << dim3(1, 1, 1), dim3(1, 1, 1) >>> (...); 1 global void sincos_kernel_2 (DATA_TYPE x, DATA_TYPE y, DATA_TYPE xy, int nx, int ny, int nz, int funcid ) { 2 int i, j, k, i n d i c e ;

47 45 3 i = getglobalidfunc [ funcid ] ( ) ; 4 for ( j = 0 ; j < ny ; ++j ) { 5 for ( k = 0 ; k < nz ; ++k ) { 6 i n d i c e = ( i ny nz ) + ( j nz ) + k ; 7 xy [ i n d i c e ] = s i n ( x [ i n d i c e ] ) + cos ( y [ i n d i c e ] ) ; 8 } 9 } 10 } Código 3.3. Código do kernel gerado com os 2 laços As nx iterações são transferidas para as dimensões de grid e blocos de threads. As iterações para 0 <= i < nx, o i é obtido com o id das threads. 1 global void sincos_kernel_1 (DATA_TYPE x, DATA_TYPE y, DATA_TYPE xy, int nx, int ny, int nz, int funcid ) { 2 int i, k, i n d i c e ; 3 i = getglobalidfunc [ funcid ] ( ) ; 4 for ( k = 0 ; k < nz ; k++) { 5 i n d i c e = ( i nz ) + k ; 6 xy [ i n d i c e ] = s i n ( x [ i n d i c e ] ) + cos ( y [ i n d i c e ] ) ; 7 } 8 } Código 3.4. Código do kernel gerado com 1 laço As (nx * ny) iterações dos laços são transferidas para o arranjos de threads. Cada thread irá executar o kernel com o código do laço mais interno, o que resulta em nz iterações e o índice é calculado como, indice = (i * nz) + k. 1 global void sincos_kernel_0 (DATA_TYPE x, DATA_TYPE y, DATA_TYPE xy, int nx, int ny, int nz, int funcid ) { 2 int i n d i c e ; 3 i n d i c e = getglobalidfunc [ funcid ] ( ) ; 4 xy [ i n d i c e ] = s i n ( x [ i n d i c e ] ) + cos ( y [ i n d i c e ] ) ; 5 } Código 3.5. Código do kernel gerado sem os laços Todas as (nx * ny * nz) iterações são transferidas para as dimensões do arranjo de threads. O indice é calculado somente com base nas dimensões do arranjo de threads. Outras transformações ou otimizações pode ser aplicadas, como a loop unroll (desenrolamento das iterações do laço) que na prática aumento o número de instruções no corpo do laço ajustando o passo do laço (stride) que no exemplo está sendo incrementado em 8.

48 46 1 global void sincos_kernel_1_unroll_8 (DATA_TYPE x, DATA_TYPE y, DATA_TYPE xy, int nx, int ny, int nz, int funcid ) { 2 int i, k, i n d i c e ; 3 i = getglobalidfunc [ funcid ] ( ) ; 4 for ( k = 0 ; k < nz ; k+=8) { 5 i n d i c e = ( i nz ) + k ; 6 xy [ i n d i c e ] = s i n ( x [ i n d i c e ] ) + cos ( y [ i n d i c e ] ) ; 7 xy [ i n d i c e + 1 ] = s i n ( x [ i n d i c e + 1 ] ) + cos ( y [ i n d i c e + 1 ] ) ; 8 xy [ i n d i c e + 2 ] = s i n ( x [ i n d i c e + 2 ] ) + cos ( y [ i n d i c e + 2 ] ) ; 9 xy [ i n d i c e + 3 ] = s i n ( x [ i n d i c e + 3 ] ) + cos ( y [ i n d i c e + 3 ] ) ; 10 xy [ i n d i c e + 4 ] = s i n ( x [ i n d i c e + 4 ] ) + cos ( y [ i n d i c e + 4 ] ) ; 11 xy [ i n d i c e + 5 ] = s i n ( x [ i n d i c e + 5 ] ) + cos ( y [ i n d i c e + 5 ] ) ; 12 xy [ i n d i c e + 6 ] = s i n ( x [ i n d i c e + 6 ] ) + cos ( y [ i n d i c e + 6 ] ) ; 13 xy [ i n d i c e + 7 ] = s i n ( x [ i n d i c e + 7 ] ) + cos ( y [ i n d i c e + 7 ] ) ; 14 } 15 } Código 3.6. Código do kernel gerado sem os laços O objetivo com essas versões dos kernels é migrar iterações dos laços para a estrutura do arranjo de threads. As versões de kernels são apresentadas na Tabela 3.3. Na primeira versão sincos_kernel_3 todas as iterações são executadas por uma única thread que executa uma cópia do kernel. Na versão sincos_kernel_2 as nx iterações do laço mais externo são transferidas para o arranjo de threads, o que resulta em nx threads executando cópias de um kernel formado com os dois laços mais internos. kernel Tabela 3.3. Versões de kernels Descrição sincos_kernel_3 Os 3 laços do código original executados: 1 configuração. O kernel que faz a execução dos 3 laços aninhados é executado por uma única thread. sincos_kernel_2 sincos_kernel_1 O kernel terá 2 laços do código original para executar. As nx iterações do laços mais externo do código original foram transferidas para o arranjo de threads. O kernel terá 1 laço do código original a ser executado. As (nx * ny) iterações dos dois laços mais externos foram transferidas para o arranjo de threads. sincos_kernel_0 Nenhum dos laços são executados. O kernel é formado somente pelo corpo do laço mais interno do aninhamento original. As (nx * ny * nz) iterações dos 3 laços aninhados foram transferidas para o arranjo de threads. Na versão sincos_kernel_1 teremos nx ny iterações transferidas para o arranjo de threads. E finalmente a versão sincos_kernel_0 tem-se nx ny nz iterações transferidas

49 47 para o arranjo, essas threads irão executar cópias do código que contém apenas as instruções do corpo do laço mais interno. A migração de iterações entre código do kernel e a estrutura do arranjo de threads possibilita a busca de um equilíbrio entre as opções que executam menos threads com mais computação ou mais threads com menos computação. Possibilitando um ajuste no código conforme a disponibilidade de recursos da arquitetura. É interessante também para que se possa ajustar o código para garantir uma certa taxa de ocupação, questões de memória compartilhada e acessos à hierarquia de memória. Quando há iterações de um ou mais laços para o arranjo de threads deve-se considerar a computação inserida pelo cálculo do id global de cada thread. Pois quanto mais dimensões utilizadas, mais operações serão necessárias para o cálculo do id global (linearização do endereço). Para exemplificar a quantidade de operações inseridas no kernel pelo cálculo do id global é apresentada na Tabela 3.4. O código que que apresenta todas as combinações para o cálculo do id global da thread está no repositório do GitHub 2 Tabela 3.4. Número de operações realizadas para o cálculo do id global das threads Função Operações add mult Total getglobalidx_1d_1d() getglobalidx_1d_2d() getglobalidx_1d_3d() getglobalidx_2d_1d() getglobalidx_2d_2d() getglobalidx_2d_3d() getglobalidx_3d_1d() getglobalidx_3d_2d() getglobalidx_3d_3d() Considerações Finais Neste capítulo foram apresentados conceitos relacionados a ajustes do código pelo lado do hardware que é verificar a escolha de uma determinada configuração de grids e blocos ou a priorização de uma das dimensões influencia no desempenho. Também foi apresentado conceito do ajustes do lado do software que tem como objetivo criar kernels que consiga mover um conjunto de laços aninhados para serem executadas em paralelo. Nos capítulos seguintes serão apresentadas a metodologia empregada para realizar os experimentos, bem como os resultados que foram obtidos. 2 <

50 Capítulo 4 Metodologia Como metodologia para o desenvolvimento do trabalho, foram estipuladas algumas atividades e um conjunto de passos para a execução de experimentos com o benchmarks e a análise dos resultados obtidos para as métricas coletadas do hardware. Os passos definidos são apresentados na Figura 4.1. Figura 4.1. Etapas para a realização da pesquisa As seções 4.1, 4.2, 4.3 e 4.4 mostrarão mais detalhes sobre cada uma das etapas, respectivamente, a escolha dos benchmarks para a execução dos experimentos, o desenvolvimento do código dos scripts para o OpenTuner, a execução dos testes e experimentos, a extração da melhor configuração pela ferramenta OpenTuner e com base nas métricas coletadas do hardware e por fim as técnicas que foram aplicadas aos resultados dos experimentos na tentativa de detectar padrões ou tendências nas configurações que pudessem influenciar no desempenho. 48

51 Uso dos benchmarks Para a realização de experimentos escolheu-se alguns benchmarks que atendessem ao critério de possuir código com dois ou mais dimensões de laços aninhados. Considerou-se na escolha algoritmos pertencentes aos conjuntos PolyBench (POUCHET et al., 2012), KernelGen (MIKUSHIN et al., 2014) e NVIDIA Cuda Samples (NVIDIA, 2009), que são benchmarks com o objetivo de execução e monitoramento de kernels utilizados em dispositivos paralelos. Foram utilizadas as versões para GPU. A escolha dos algoritmos dentro desse critério foi feita pois a ideia é testar a transferência ou migração de iterações dos laços para o arranjo de threads. Analisando e ajustando a relação do número de threads com o kernel correspondente. Assim, foram escolhidos os algoritmos sumvector, gemm e o sincos Algoritmo Soma de Vetores - sumvector O primeiro algoritmo utilizado foi o algoritmo soma de vetores do conjunto de exemplos da NVIDIA (CUDA Samples), esse conjunto vem com a instalação do kit de desenvolvimento da plataforma CUDA. Foi utilizado inicialmente por ser um algoritmo simples e usado em tutoriais de programação paralela. Seu funcionamento consiste, de três vetores (A, B, C) de tamanho N, sendo que, cada posição do vetor A soma-se com a posição do vetor B, e o resultado é armazenado no vetor C. No Código 4.1 pode-se visualizar a implementação do algoritmo soma de vetor na linguagem de programação C int sumvector ( int a [N], int b [N] ) { 3 int c [N], i ; 4 5 for ( i =0; i <N; i ++){ 6 c [ i ] = a [ i ] + b [ i ] ; 7 } 8 p r i n t _ i n t e r a c o e s ( c ) ; 9 } Código 4.1. Exemplo de código em C do algoritmo soma de vetor O algoritmo no formato paralelo na versão CUDA, pode ser visualizado no Código 4.2. Seu funcionamento é comparável à implementação na linguagem de programação C, contudo o laço de repetição foi substituído pelo arranjo de threads. Durante a execução cada thread executará uma soma de um elemento de A com um elemento de B. A condição na linha 6 do

52 50 código assegura que não estão sendo executadas mais threads do que o tamanho da dimensão dos vetores, que neste caso é (N) global void vecadd (DATA_TYPE a, DATA_TYPE b, DATA_TYPE c, int n, int funcid ) { 3 // Thread ID 4 int id = getglobalidfunc [ funcid ] ( ) ; 5 i f ( id < n ) 6 c [ id ] = a [ id ] + b [ id ] ; 7 } 8... Código 4.2. Exemplo de código em CUDA do algoritmo soma de vetor Por ser um algoritmo simples e de funcionamento de fácil compreensão, ele foi utilizado para explorarmos o problema da escolha das configurações, bem como demonstrar que realizar escolha aleatórias de configurações não é uma tarefa correta, pois pode levar à subutilização dos recursos da GPU, causando a degradação do desempenho. Na Seção 5.1 são mostrados os resultados obtidos através de execuções avulsas com escolhas aleatórias. O código completo escrito na linguagem C utilizando as bibliotecas do CUDA pode ser visto no repositório no GitHub Algoritmo GEMM O GEMM é um algoritmo comum em Álgebra Linear, Aprendizado de Máquina, Estatística e muitos outros domínios. Seu funcionamento consiste em três matrizes (A,B,C) de tamanho N na qual a matriz C é resultante de duas multiplicações sendo a primeira entre uma constante α AB e depois o próprio C multiplicado por uma constante β. Sua equação geral é apresentada da seguinte forma, C αab + βc (POUCHET et al., 2012). A implementação em C pode ser vista no Código 4.3, no qual foi utilizado três laços de repetição onde os dois primeiros são utilizados para percorrer a matriz C e realizar a multiplicação de cada valor da matriz C com o valor declarado de beta. Após esse primeiro laço é realizado a multiplicação entre alpha com as matrizes A e B e logo após é realizado a soma com os valores da matriz C e armazenado na própria matriz C. 1 #define alpha 32412; 2 #define beta 2123; int gemm(data_type a, DATA_TYPE b, DATA_TYPE c, int ni, int nj, int nk, int alpha, int beta ) { 3 <

53 51 5 for ( i = 0 ; i < ni ; i ++) 6 for ( j = 0 ; j < nj ; j++){ 7 C[ i ] [ j ] = beta ; 8 for ( k = 0 ; k < nk ; ++k ) 9 C[ i ] [ j ] += alpha A[ i ] [ k ] B[ k ] [ j ] ; 10 } 11 } Código 4.3. Exemplo de código em C do algoritmo GEMM No Código 4.4 é apresentado o exemplo do kernel em CUDA. Seu funcionamento consiste primeiro na definição das constantes α e β, aqui com valores estipulados pela biblioteca do PolyBench. Após a declaração das constantes é declarado a função kernel que recebe como parâmetros os três vetores (a, b, c) com os respectivos tamanhos (NI, NJ, NK) e os laços de repetição que seriam utilizados para realizar as operações são trocados por arranjos de threads a partir da função getglobalfunc. O restante do código é apresentado no repositório GitHub 4. 1 #define ALPHA f 2 #define BETA f 3 global void gemm_kernel (DATA_TYPE a, DATA_TYPE b, DATA_TYPE c, int NI, int NJ, int NK, int funcid ) { 4 int j = getglobalidfunc [ funcid ] ( ) ; 5 int i = getglobalidfunc [ funcid ] ( ) ; 6 i f ( ( i < NI ) && ( j < NJ) ) { 7 c [ i NJ + j ] = BETA; 8 int k ; 9 for ( k=0; k < NK; k++){ 10 c [ i NJ + j ] += ALPHA a [ i NK + k ] b [ k NJ +j ] ; 11 } 12 } 13 } Código 4.4. Exemplo de código do kernel em CUDA do algoritmo GEMM Com o algoritmo GEMM em funcionamento, foi criado o código para a ferramenta OpenTuner que será explicado na Seção 4.2. As seções e mostram respectivamente os resultados obtidos pela ferramenta OpenTuner para o algoritmo GEMM, e também o resultado obtido através de testes por força-bruta. 4 < gemm-occupancy/gemm.cu>

54 Algoritmo de Cálculo da Tabela de Senos e Cossenos sincos O algoritmo sincos atualmente é implementado por benchmarks com propósitos matemáticos (álgebra linear) e também para desenvolvimentos de ferramentas como o KernelGen que tem como objetivo geração automático de kernels paralelos. Seu funcionamento é apresentado na Seção 3.2 (Ajuste do lado do software), a qual apresenta o sincos implementado na linguagem C (Código 3.1) e também suas versões de kernels para CUDA. Algumas versões de kernels foram geradas para sincos, essas versões são explicadas na Tabela 3.3. O sincos foi utilizado com o objetivo de mostrar as possibilidades de ajustes do lado do software, cada uma das versões de kernels faz a migração de iterações de uma das dimensões dos laços aninhados. Os testes com essas versões foram realizados com auxílio do OpenTuner e as melhores configurações foram encontradas pelas heurísticas implementadas pelo OpenTuner com base nos resultados obtidos para as métricas coletadas. Na Seção são apresentados os resultados para as execuções com o sincos Desenvolvimento dos scripts para as execuções com o OpenTuner Com a escolha dos algoritmos, o próximo passo foi desenvolver scripts em Python para a ferramenta OpenTuner que tem como função a execução dos benchmarks com cada uma das configurações e então escolher a melhor configuração com base no resultado de métricas coletadas do hardware pela ferramenta de perfilamento nvprof. Vale ressaltar que foi criado um script para cada métrica e para cada algoritmo. Nesta seção será apresentado somente o script desenvolvido para o algoritmo GEMM, os demais scripts podem ser consultados no repositório do GitHub 5. Como demonstrado na Seção 2.4.1, o OpenTuner trabalha em três passos, definição do espaço de busca, método manipulator que realiza as escolhas e o método run que realiza a execução e a escolha do mélhor resultado a partir da métrica escolhida. Na primeira versão desenvolvida para o OpenTuner no algoritmo GEMM foi especificado o espaço de busca aberto na qual ele buscava todos os valores dentro de um espaço mínimo e máximo, esse mínimo e máximo é denotado por um bloco de parâmetros que pode ser visto no Código <

55 BLOCO_PARAMETROS = [ 3 ( kernel, 0, 0), 4 ( gx, 1, ), 5 ( gy, 1,65535), 6 ( gz, 1,65535), 7 ( bx, 1,1024), 8 ( by, 1,1024), 9 ( bz, 1,64), 10 ( ni, 64,64), 11 ( nj, 64,64), 12 ( nk, 64,64), 13 ( gpuid, 0, 0) 14 ] Código 4.5. Espaço de busca aberto implementado no OpenTuner Com esse bloco de parâmetros criado o método run escolhia um valor dentro do mínimo e máximo e realizava a execução do algoritmo GEMM para as configurações escolhidas. Contudo, na maneira de busca aberta ocasionava que a maioria das escolhas era escolhas inválidas (escolhas que não seguem as regras especificadas na Seção 3.1), ocasionando em testes errados, bem como o tempo gasto para a execução era alto, fazendo com que mudássemos a forma de buscar as configurações. Assim, foi desenvolvido uma segunda versão que buscaria somente no espaço de busca de configurações válidas. Desta forma foi criado primeiramente um script na linguagem C que escreve em um arquivo todas as configurações válidas para um tamanho N, logo após o OpenTuner cria uma lista destas configurações e encaminha para o método run que realiza os testes. O Código 4.6 apresenta parte do script desenvolvido em C para escrever em um arquivo as configurações válidas e o Código 4.7 mostra uma parte do arquivo criado pelo script em C. Pode ser visualizado todo o script e os arquivos gerados no repositório do GitHub for ( bz = 1 ; bz <= min ( i t e r a t i o n s,max_block_z) ; bz++) { 3 for ( by = 1 ; by <= min ( ( i t e r a t i o n s / bz ),MAX_BLOCK_Y) ; by++) { 4 for ( bx = 1 ; bx <= min ( ( ( i t e r a t i o n s / bz ) / by ),MAX_BLOCK_X) ; bx++) { 5 for ( gx = 1 ; gx <= min ( ( ( ( i t e r a t i o n s / bz ) / by ) / bx ),MAX_GRID_X) ; gx++) { 6 for ( gy = 1 ; gy <= min ( ( ( ( ( i t e r a t i o n s / bz ) / by ) / bx ) / gx ), MAX_GRID_Y) ; gy++) { 7 for ( gz = 1 ; gz <= min ( ( ( ( ( ( i t e r a t i o n s / bz ) / by ) / bx ) / gx ) / gy ),MAX_GRID_Z) ; gz++) { 6 <

56 54 8 confblock = bx by bz ; 9 confgrid = gx gy gz ; 10 c o n f i g = confblock confgrid ; 11 // Evict k e r n e l divergence, b l o c k s with multiply warp s i z e. 12 i f ( ( confblock <= 1024) && ( c o n f i g == i t e r a t i o n s ) && ( confblock % 32 == 0) ) { 13 p r i n t f ( " gx:%d, gy:%d, gz:%d, bx:%d, by:%d, bz:%d, \n ", 14 countconfig++; 15 } 16 } 17 } 18 } 19 } 20 } 21 } gx, gy, gz, bx, by, bz ) ; Código 4.6. Espaço de busca aberto implementado no OpenTuner 2 gx:1, gy:16, gz:8, bx:32, by:1, bz:1, 3 gx:1, gy:32, gz:4, bx:32, by:1, bz:1, 4 gx:1, gy:64, gz:2, bx:32, by:1, bz:1, 5 gx:1, gy:128, gz:1, bx:32, by:1, bz:1, 6 gx:2, gy:1, gz:64, bx:32, by:1, bz:1, 7... Código 4.7. Parte do arquivo criado com as configurações válidas para N Com a criação do espaço de busca é enviado uma lista para o método run com os valores dos grids e blocos onde ele estará realizando os testes e os "cortes"que julgar necessário através dos algoritmos de busca. Exemplo do método run é apresentado no Código 4.8. Com este método funcionando o OpenTuner cria um comando em linha de comando no formato do Código while True : 3 c o n f i g u r a t i o n = d e s i r e d _ r e s u l t. c o n f i g u r a t i o n. data 4 print "Configuration: ", c o n f i g u r a t i o n 5 c f g = { match. group ( 1 ) : match. group ( 2 ) for match in re. f i n d i t e r ( r"([^:]+) :(\S+)\s*,[ \n]", c o n f i g u r a t i o n [ config ] ) } 6 print "CFG: ", c f g 7 confblock = int ( c f g [ bx ] ) int ( c f g [ by ] ) int ( c f g [ bz ] ) 8 confgrid = int ( c f g [ " gx" ] ) int ( c f g [ gy ] ) int ( c f g [ gz ] )

57 55 9 c o n f i g = confblock confgrid 10 print "ConfBlock "+ str ( confblock ) 11 print "ConfGrid " + str ( confgrid ) 12 i f ( ( confblock <= 1024) and ( confblock % 32 == 0) and ( c o n f i g == n ) ) : 13 break 14 else : 15 return Result ( time=fail_penalty) 16 print "compiled: ", true i f compiled else false 17 i f not compiled : 18 print " Compiling the program..." 19 gcc_cmd = nvcc -I / usr/ local/ cuda/ include -L / usr/ local/ cuda/ lib64 - ccbin=g++-6 gemm. cu - lcuda -lm -o gemm - cuda. exe 20 compile_result = s e l f. call_program ( gcc_cmd ) 21 a s s e r t compile_result [ returncode ] == 0 22 print " OK.\ n" 23 global compiled 24 compiled = not compiled 25 run_cmd = nvprof -- metrics achieved_occupancy./ gemm - cuda. exe Código 4.8. Exemplo do método run para o algoritmo GEMM Com a execução do comando gerado a partir do método run é enviado para o método get_metric_from_app_output que verifica o valor que foi gerado e retorna um valor da métrica que é usado para realizar a comparação e os "cortes"com os algoritmos de busca. Em nossa implementação neste método fica especificado a criação de um arquivo.csv que contém todas as configurações e resultados gerados, no Código 4.9 é demonstrado a implementação do método get_metric_from_app_output. Quando encontrado a melhor configuração é gerado um arquivo.json contendo a melhor configuração def get_metric_from_app_output ( s e l f, app_output, c o n f i g u r a t i o n, kernel, gpuid ) : 3 metric_value = l i n e s = app_output. s p l i t ( "\n" ) 5 for c u r r e n t _ l i n e in l i n e s : 6 s t r g = "" + c u r r e n t _ l i n e 7 i f s t r g. f i n d ( "Occupancy" ) > 1: 8 idx = s t r g. index ( "Occupancy" ) 9 s u b s r t g = s t r g [ idx : ]. s p l i t ( " " ) 10 print "substrg: ", s u b s r t g 11 metric_value = f loat ( s u b s r t g [ 3 ] ) 12 print "achieved_occupancy: ", metric_value 13 c o n f i g u r a t i o n = str ( c o n f i g u r a t i o n )

58 56 14 c o n f i g u r a t i o n = c o n f i g u r a t i o n. r e p l a c e ( "{", str ( k e r n e l )+"," ). r e p l a c e ( ":", " " ). r e p l a c e ( "}", "" ) 15 c o n f i g u r a t i o n = c o n f i g u r a t i o n. r e p l a c e ( " gx", "" ). r e p l a c e ( " gy ", "" ). r e p l a c e ( " gz ", "" ). r e p l a c e ( " bx ", "" ). r e p l a c e ( " by ", "" ). r e p l a c e ( " bz ", "" ). r e p l a c e ( " ", "" ). r e p l a c e ( "\"", "" ) 16 r e s u l t a d o = metric_value 17 arquivo_csv = open( "diretorio_saida"+str ( sys. argv [ 2 ] )+".csv", "a" ) 18 arquivo_csv. w r i t e ( "Kernel,gx,gy,gz,bx,by,bz,gpuId,occupancy \n" ) 19 arquivo_csv. w r i t e ( str ( c o n f i g u r a t i o n )+","+str ( gpuid )+","+str ( r e s u l t a d o )+" \n" ) 20 return metric_value Código 4.9. Exemplo do método get_metric_from_app_output para o algoritmo GEMM Com o funcionamento exato do código é criado um arquivo.csv contendo as configurações e também um arquivo.json com a melhor configuração encontrada. No Código 4.10 é visto exemplo dos dois arquivos para N = 64 para a métrica achieved_occupancy Arquivo. csv 2 Kernel, gx, gy, gz, bx, by, bz, gpuid, occupancy 3 0, 4, 2, 2, 3 2, 2, 4, 0, , 8, 2, 4, 8, 4, 2, 0, , 2, 1, 1 6, 1, 1 2 8, 1, 0, Arquivo. j s o n 8 {"ni" : 64, "nj" : 64, "nk" : 64, "kernel" : 0, "gpuid" : 0, "config" : " gx:64, 9... gy:1, gz:2, bx:1, by:2, bz:16, \n"} Código Parte do arquivo criado com as configurações válidas para N Para uma melhor organização foi criado um código OpenTuner para cada métrica que foi utilizada para gerar resultados, no presente texto é apresentado somente parte do código para a métrica achieved_occupancy, para visualizar os outros códigos bem como este código completo, pode ser visualizado no repositório do GitHub 7. Vale ressaltar que na próxima Seção 4.3 é apresentado as GPUS que foram utilizadas para os testes e também as métricas utilizadas para a escolha da melhor configuração no OpenTuner. 7 <

59 Realização dos Testes e Extração da Melhor Configuração Com a escolha dos algoritmos, e o desenvolvimento dos códigos para o OpenTuner, foi especificado quais métricas deveriam ser utilizados para a escolha da melhor configuração. Essas métricas são da ferramenta nvprof que a partir de uma execução de linha de comando (especificado no Código 5.2) é retornado um valor ou uma porcentagem dependendo da métrica escolhida. As métricas que foram escolhidas para determinar as melhores configurações do presente trabalho são: sm_efficiency: porcentagem de tempo que pelo menos um warp está ativo no multiprocessador. achieved_occupancy: razão entre a média de warps ativos por ciclo com o máximo número de warps suportado no multiprocessador. ipc: instruções executadas por ciclo. inst_per_warp: número médio de instruções executados por cada warp. gld_efficiency: proporção da taxa de transferência solicitada da memória global (load) para a taxa de transferência na memória global necessária. gst_efficiency: proporção da taxa de transferência de armazenamento na memória global (store) solicitada pela taxa de transferência de armazenamento de memória global necessária. Para ter uma comparação de arquiteturas, os testes foram executados em três GPUs que são da arquitetura Kepler e da arquitetura Pascal. Desta forma estará comparando quais as melhores configurações de kernel para a determinada arquitetura. A Figura 4.2 apresenta as configurações das três GPUs escolhidas. Figura 4.2. Arquiteturas que serão utilizadas nos experimentos A partir da execução dos testes para os algoritmos explicados nas seções anteriores, o próximo passo é encontrar padrões ou tendências de configurações. A Seção 5.1, mostra os resultados obtidos a partir de escolhas avulsas para o algoritmo soma de vetor, as Seções 5.2.1,

60 , e mostram os resultados obtidos com o auxílio da da ferramenta OpenTuner para os algoritmos sincos e GEMM, bem como apresenta resultados obtidos através de força bruta para os dois algoritmos Detecção de Padrões ou Tendências de Configuração Assim que concluído todos os testes, foi pesquisado uma forma de encontrarmos um padrão ou uma tendência de configuração a partir de todos os dados que tínhamos dos testes realizados. Desta forma, foi realizado uma análise dos dados, com o auxílio da linguagem de programação R. No Código 4.11 é apresentado parte do script criado para a análise de dados, todo o restante do script pode ser encontrado no GitHub mypath < "/home/tcc_cuda_opentuner_joao/pattern_ia/testes/gemm - smefficiency -512-todas -conf -gtx780.csv" 3 4 dados < read. csv ( mypath, header = T, sep = "," ) 5 i s. data. frame ( dados ) 6 head ( dados ) 7 s t r ( dados ) 8 summary( dados ) 9 10 # adiciona i n t e r a c a o e n t r e g e b 11 dados < transform ( dados, 12 g=interaction ( gx, gy, gz ), 13 b=interaction ( bx, by, bz ) ) # limpa os n i v e i s nao u t i l i z a d o s 16 dados < d r o p l e v e l s ( dados ) ## usando funcoes da b i b l i o t e c a l a t t i c e 19 library ( l a t t i c e ) x11 ( ) 22 xyplot ( s m e f f i c i e n c y ~ g, data = dados, 23 as. table = TRUE, 24 s c a l e s = l i s t ( y = l i s t ( cex = 1), 25 x = l i s t ( cex = 0. 5, r o t = 45) ) 26 # l a y o u t = c (22,20) 27 ) 28 8 <

61 dados$cut. e f i c < with ( dados, cut ( s m e f f i c i e n c y, 32 breaks = c ( 0, 1 8, 30, 60, 80, 100) ) ) with ( dados, table ( cut. e f i c ) ) dados < subset ( dados, cut. e f i c == "(80,100]" ) Código Parte do script em R criado para análise de dados. Primeiro foi verificado a validade dos dados através da função is.data.frame, desta forma ele especifica o cabeçalho do arquivo.csv e aplica-se a função sumarização para verificar os formatos dos dados do dataframe. Com a sumarização criamos dentro do próprio dataframe uma combinação (chamada de interaction) combinando todos os valores de grids para a variável g e os blocos para a variável b. Após a combinação foi feito a limpeza dos níveis não utilizados e com esta limpeza foi chamado a função xyplot que foi passado como parâmetro os valores da métrica sm_efficiency, combinando com os dados. No repositório do GitHub 9 pode visualizar todo o script desenvolvido. Contudo com os experimentos realizados não conseguimos encontrar padrões e tendências de configuração, na Seção é demonstrado os resultados obtidos até o momento Considerações Finais Neste capítulo foi apresentado a metodologia aplicada para a realização deste trabalho. Foi apresentado as bibliotecas de algoritmos que serão utilizados, como foi realizado o desenvolvimento dos script para os testes com a ferramenta OpenTuner, a realização dos testes que apresentou as métricas para escolha das configurações e as GPUs usadas no experimento. Por fim, é explicado como foi feito a procura por padrões ou tendências de configuração. Mais detalhes dos experimentos realizados e dos resultados obtidos são apresentados no Capítulo 5. 9 <

62 Capítulo 5 Experimentos e Resultados Durante o desenvolvimento deste trabalho foram executados alguns experimentos. Experimentos iniciais com execuções avulsas para verificações básicas e foram executados experimentos com alguns benchmarks selecionados de acordo com o critério de possuírem um conjunto de laços aninhados. A execução dos experimentos foi feita com base nos passos estabelecidos no Capítulo 4 e os resultados obtidos apresentados neste Capítulo Primeiros Resultados Esta seção tem como finalidade exibir os primeiros resultados obtidos. A Seção foi utilizado o algoritmo soma de vetores. A simplicidade desse algoritmo permite mostrar que, mesmo em casos simples, se o desenvolvedor fizer uma escolha aleatória para os parâmetros da configuração do arranjo de threads, esta escolha poderá não ser uma das melhores configurações. A Seção apresenta a ideia das diferentes dimensões do arranjo de threads e também apresenta alguns resultados nos quais esta diferença se torna perceptível pela utilização de kernels com códigos distintos. Por fim, a Seção mostra resultados obtidos utilizando a ideia de ajustar as configurações de kernels, pelos métodos de força bruta e pelo uso da ferramenta de autotuning OpenTuner Experimento com execuções avulsas para o Soma de Vetores Foi criado um experimento inicial para verificar a dificuldade em encontrar a melhor configuração de kernel para uma arquitetura. Nesse primeiro experimento foi usado o algoritmo de soma de vetores, que tem sido utilizado em tutoriais de programação paralela por ser um algoritmo simples e fácil de verificar a divisão de trabalho pelas threads, o 60

63 61 funcionamento do algoritmo soma de vetor é explicado na Seção Para exemplificar a complexidade na busca pela melhor configuração foi escolhido N = para o tamanho dos vetores. Esse valor foi escolhido por ser um tamanho considerável médio e que apresenta resultados satisfatórios em curto tempo de execução. Neste experimento foram escolhidas configurações aleatórias dentro de todas as possibilidades para o tamanho Para este tamanho há em torno de configurações possíveis de serem feitas, sendo que foram escolhidas para os testes duas configurações de cada dimensão (1D_1D, 2D_2D e 1D_3D). Assim, os testes foram realizados escolhendo-se as configurações de maneira aleatória e verificamos que não se consegue a configuração ideal para o algoritmo e a arquitetura em questão. O experimento foi executado em duas das três GPUs apresentadas na Figura 4.2 e a métrica escolhida para a execução foi a achieved_occupancy que representa a ocupação dos multiprocessadores, retornando a relação média de warps ativos por execução em relação ao tamanho máximo suportado em um multiprocessador. Os resultados para o experimento com o soma de vetores são apresentados na Tabela 5.1. Tabela 5.1. Resultados do Experimento soma de vetor N GTX Kepler TitanX - Pascal Configuração Resultado Configuração Resultado (1024, 1, 1),(1, 128, 1) 82, 68% (1024, 1, 1),(1, 128, 1) 84, 76% (1, 1024, 1), (128, 1) 83, 34% (1, 1024, 1), (128, 1) 78, 15% (128, 1, 1), (1, 16, 64) 87, 00% (128, 1, 1), (1, 16, 64) 86, 44% (256, 1, 1), (1, 8, 64) 83, 37% (256, 1, 1), (1, 8, 64) 88, 61% (128, 1, 1), (2, 8, 64) 81, 22% (128, 1, 1), (2, 8, 64) 86, 67% (128, 1, 1), (2, 8, 64) 81, 42% (128, 1, 1), (2, 8, 64) 86, 92% (32, 128, 1), (1, 1, 32) 21, 10% (32, 128, 1), (1, 1, 32) 23, 14% ( 1, 32, 128), (1, 1, 32) 21, 01% ( 1, 32, 128), (1, 1, 32) 22, 50% ( 1, 16, 8), (1, 16, 64) 84, 42% ( 1, 16, 8), (1, 16, 64) 78, 29% ( 1, 64, 2), (1, 16, 64) 84, 31% ( 1, 64, 2), (1, 16, 64) 85, 41% (16, 1, 8), (4, 4, 64) 82, 91% (16, 1, 8), (4, 4, 64) 85, 76% (128, 1, 2), (2, 4, 64) 84, 02% (128, 1, 2), (2, 4, 64) 88, 60% (64, 16, 2), (1, 1, 64) 41, 99% (64, 16, 2), (1, 1, 64) 57, 29% ( 2, 256, 8), (1, 1, 32) 21, 11% ( 2, 256, 8), (1, 1, 32) 22, 72% ( 4, 16, 2), (1, 16, 64) 83, 99% ( 4, 16, 2), (1, 16, 64) 85, 90% (32, 2, 2), (1, 64, 16) 82, 40% (32, 2, 2), (1, 64, 16) 85, 42% ( 2, 32, 2), (4, 4, 64) 82, 32% ( 2, 32, 2), (4, 4, 64) 86, 74% (16, 4, 2), (2, 8, 64) 82, 98% (16, 4, 2), (2, 8, 64) 87, 12%

64 62 Fica evidente que escolhendo aleatoriamente configurações para um tamanho N é improvável que encontre a melhor configuração para métrica achieved_occupancy, pois para esta métrica os melhores resultados teriam valores o mais próximo de 100%. Atingindo uma taxa elevada de ocupação dos multiprocessadores e de seus recursos. Diante da dificuldade de se encontrar uma boa configuração de maneira aleatória, considerando o número de configurações que precisariam ser testadas, o processo manual torna-se inviável. Então optou-se por automatizar inicialmente via scripts e depois com o uso de ferramenta de autotuning que mostrou-se uma alternativa interessante, pois a ferramenta explora o espaço de soluções através de buscas e ao final do processo encontra a melhor configuração com base nos resultados da métrica considerada. As seções seguintes apresentam os experimentos iniciais Experimento com execuções avulsas para o sincos Também para exemplificar o quanto que as dimensões são utilizadas, foi executado experimentos com a métrica achieved_occupancy que na qual mostrou as médias de warps ativos durante a execução. Para essa amostra foi utilizado as duas GPUs da Figura 4.2 de arquiteturas diferentes para uma comparação do uso de toda a capacidade da arquitetura. Os resultados são apresentados na Tabelas 5.2 e 5.3. Tabela 5.2. Média de warps ativos na execução do algoritmo sincos GTX Kepler nx, ny, nz Kernel Config Dimens. Result (576,32,18),(32,18,1) 3D_2D 75% (32,18,1),(32,18,1) 2D_2D 82% (1,1,1),(32,18,1) 1D_2D ERROR (1,1,1),(1,1,1) 1D_1D ERROR Tabela 5.3. Média de warps ativos na execução do algoritmo sincos Titan X - Pascal nx, ny, nz Kernel Config. Dimens. Result (960,32,30),(32,30,1) 3D_3D 78% (30,32,1),(30,32,1) 2D_2D 93% (1,1,1),(32,30,1) 1D_2D 45% (1,1,1),(1,1,1) 1D_1D 1% Na Tabela 5.2 foi utilizado o tamanho 576 para as iterações (nx, ny, nx). Este valor foi utilizado por ser o máximo que a memória do dispositivo possibilita, sendo que usando o

65 63 tamanho em 576, foram utilizados 2, 29 GBs de um total disponível de 3 GBs. O kernel 1 foi o que obteve-se o melhor resultado, sendo que, o kernel 2 as iterações nx, ny serão executados pelas threads, desta forma, com as divisões de grids e blocos no formato mostrado, houve uma utilização de 82% dos warps ativos na execução. Já na Tabela 5.3 foi utilizado o tamanho de 960 pois neste dispositivo possui uma memória de 12 GBs e neste teste foram utilizados 10 GBs. O kernel 1 foi que obteve melhor utilização de warps, este resultado é explicado pela divisão de threads para as iterações de nx, ny, essa divisão também pode explicar porque o kernel 3 obteve o pior desempenho, sendo que sua divisão de grids e blocos foi o mínimo (1 em todos os casos), desta forma somente um bloco de thread irá executar os laços Experimentos iniciais de automatização por força bruta Visando automatizar a execução dos testes de configurações (grid, bloco, threads), inicialmente desenvolveu-se um código para gerar scripts bash que disparassem a execução dos benchmarks, por meio do método de força bruta. Neste método é executado todas as configurações possíveis e ao final compara-se o resultado obtido para encontrar a melhor configuração. Nesse primeiro experimento foi utilizada a linguagem de programação C, por ser uma linguagem compilada o que torna a execução mais rápida e também por ser uma linguagem bem consolidada atualmente, o Código 5.1 apresenta a codificação realizada para o experimento por força bruta. Primeiro foram definidos os tamanhos máximos dos blocos e grids (linhas 5-10), esses valores são os apresentados pela arquitetura que será utilizada para o experimento. Após definir os tamanhos dos grids e blocos foi definido uma função mínimo que realiza a comparação e escolhe o menor tamanho, esta função é utilizada nos laços para escolher o mínimo entre o número de iterações que o algoritmo irá realizar com o máximo dos blocos e grids. Na função principal (linhas 13 44) é desenvolvido todo o algoritmo, sendo que, primeiro declarado as variáveis que serão utilizadas para armazenar as configurações (gx, gy, gz, bx, by e bz), também as variáveis (confgrid, config, contconfig e confblock) que respectivamente armazenavam a multiplicação entre os grids, a multiplicação entre blocos e grids, o total de configurações possíveis e a multiplicação dos blocos. A parte da geração das configurações está no laços entre as linhas 22 42, sendo que, cada laço gera a configuração para um bloco ou grid. Por exemplo o primeiro laço (linha 22) realiza a iteração de 1 até o menor valor entre o tamanho do vetor n e o MAX_BLOCK_Z. Nos demais laços é realizado o mesmo formato, porém com uma diferença que é a divisão de n pelo valor encontrado no laço anterior e a partir desta divisão se encontra o menor valor

66 64 com o máximo de grids ou blocos. Este formato de iteração juntamente com a condição na linha 31 garante que será criado somente configurações válidas. 1 #include <s t d i o. h> 2 #include <s t d l i b. h> 3 #include <math. h> 4 5 #define MAX_BLOCK_X #define MAX_BLOCK_Y #define MAX_BLOCK_Z #define MAX_GRID_X #define MAX_GRID_Y #define MAX_GRID_Z #define min ( x, y ) ( ( x ) <(y )?( x ) : ( y ) ) int main ( int argc, char const argv [ ] ) { 14 unsigned long long int gx, gy, gz, bx, by, bz, confblock ; 15 unsigned long long int confgrid, c o n f i g, contconfig ; 16 unsigned int n ; 17 n = a t o i ( argv [ 1 ] ) ; 18 confblock = 0 ; 19 confgrid = 0 ; 20 c o n f i g = 0 ; 21 contconfig = 0 ; 22 for ( bz = 1 ; bz <= min ( n, MAX_BLOCK_Z) ; bz++){ 23 for ( by = 1 ; by <= min ( ( n / bz ), MAX_BLOCK_Y) ; by++){ 24 for ( bx = 1 ; bx < min ( ( ( n / bz ) / by ), MAX_BLOCK_X) ; bx++){ 25 for ( gx = 1 ; gx <= min ( ( ( ( n / bz ) / by ) / bx ),MAX_GRID_X) ; gx++){ 26 for ( gy = 1 ; gy <= min ( ( ( ( ( n/ bz ) / by ) / bx ) / gx ),MAX_GRID_Y) ; gy++){ 27 for ( gz =1; gz <= min ( ( ( ( ( ( n / bz ) /by ) / bx ) / gx ) / gy ),MAX_GRID_Z) ; gz++){ 28 confblock = bx by bz ; 29 confgrid = gx gy gz ; 30 c o n f i g = confblock confgrid ; 31 i f ( ( confblock <= 1024) && ( c o n f i g == n ) && ( confblock% 32 ==0)) { 32 p r i n t f ( " (%4d,%4d,%4d,%4d,%4d,%4d ) \n ", gx, gy, gz, bx, by, bz ) ; 33 contconfig++; 34 } } 37 } 38 } 39 } 40 } 41 } 42 return ( 0 ) ; 43 } Código 5.1. Exemplo de algoritmo para encontrar melhor configuração por força-bruta A partir da geração das configurações é executado o código do benchmark implementado em CUDA com os parâmetros sendo os valores gerados pelo Código 5.1 juntamente com a métrica achieved_occupancy, o Código 5.2 mostra como é a execução por linha de comando da soma de vetor com as configurações e as métricas.

67 65 1 nvprof -- metrics achieved_occupancy./ sumvector - cuda <kernel > <gx > <gy > <gz > <bx > <by > < bz > <n> <dimensao > <gpu > Código 5.2. Formato de execução da soma de vetor em CUDA com as configurações geradas Contudo, o método de escolha por força bruta não é de longe a melhor solução, pois para determinados tamanhos de n a busca se torna complexa e a escolha da melhor solução não é encontrada em tempo hábil. Por exemplo, somente para gerar as configurações para o tamanho de n = foram gastos cerca de 5 minutos, e para tamanhos de n maiores a geração e a execução pode chegar a meses. Desta forma, uma maneira mais adequada de se encontrar a melhor configuração é utilizando-se ferramentas de autotuning, pois estas utilizam-se de algoritmos de busca que realizam escolhas com base em heurísticas pré-determinadas que exploram o espaço de configurações mais eficientemente Experimentos Essa seção apresenta os resultados gerados pelo algoritmo sincos e GEMM com o uso da ferramenta OpenTuner. O algoritmo sincos apresentou valores satisfatórios com até possíveis visualizações de padrões ou tendências de configuração. O algoritmo GEMM demonstrou valores considerados bons, contudo por ser um algoritmo que não usa uma intensidade computacional alta, trouxe valores abaixo do esperado Experimento 1 com OpenTuner: Sincos As Seções a seguir é discutido respectivamente os resultados dos testes do algoritmo sincos com as métricas gld_efficiency, gst_efficiency, inst_per_warp, ipc, achieved_occupancy e sm_efficiency. Todos os resultados gerados, podem ser visualizados no repositório do GitHub 10, sendo que todas as configurações testadas foram armazenadas em arquivos no formato.csv e os melhores resultados foram armazenados em arquivos no formato.json. Resultados do algoritmo Sincos com a métrica gld_efficiency Segundo Cheng et al. (2014), usando-se a métrica gld_efficiency é retornado a taxa de transferência solicitada da memória global para a taxa de transferência da memória global necessária, isto é, mede o quão bem as operações de carregamento do kernel usam a largura de banda da memória do dispositivo, assim para validarmos que a configuração de fato está no seu melhor desempenho, esta tem que obter o resultado próximo a 100%. As melhores configurações obtidas para a métrica gld_efficiency nas GPUS - TitanX, GTX1070 e GTX780 são apresentadas nas Tabelas 5.4, 5.5 e 5.6. Vale ressaltar que a ferramenta 10 <

68 66 OpenTuner retorna somente uma configuração como a melhor configuração, assim, pode haver outras configurações que obtiveram o mesmo valor e que não foram listadas na tabela. Tabela 5.4. Sincos métrica gld_efficiency GPU-TitanX N Kernel gx gy gz bx by bz gld_efficiency % % % % % % % % % % Tabela 5.5. Sincos métrica gld_efficiency GPU-GTX-1070 N Kernel gx gy gz bx by bz gld_efficiency % % % % % % % % % % Para todos os tamanhos de N obteve-se o resultado de 90, 28% de uso da memória global da GPU, contudo houve uma variância de valores entre 12.5% a 90.28%. Por exemplo, a configuração (1, 1, 4, 1, 32, 32) para N = 64 obteve-se o valor de 90, 28%, contudo, se verificarmos as configurações (1, 1, 4, 32, 1, 32) e (1, 1, 4, 32, 32, 1), estas obtiveram o valor de 12, 5%. Assim como quando foram modificados os valores das dimensões para o grid obteve-se valores de 12, 5%, como no caso da configuração (1, 1, 32, 32, 4, 1). Essas diferenças ocorrem também para outros Ns, como exemplo para N = 352 sendo que a melhor configuração foi (4, 2, 44, 4, 2, 44) com 90, 28%, mudando as configurações de blocos (4, 2, 44, 2, 4, 44) e (4, 2, 44, 44, 2, 4) ambos obtém o valor de 12, 5%. Uma possível resposta está embasado por Cheng et al. (2014), para essa mudança se deve ao fato que os valores dos blocos são maiores que um warp, sendo que para melhorar o desempenho a dimensão mais interna deve ser um múltiplo do tamanho do warp, isto é múltiplo de 32.

69 67 Tabela 5.6. Sincos métrica gld_efficiency GPU-GTX-780 N Kernel gx gy gz bx by bz gld_efficiency % % % % % % % % % % Resultados do algoritmo Sincos com a métrica gst_efficiency De acordo com Cheng et al. (2014), a métrica gst_efficiency retorna a porcentagem de uso da memória global de armazenamento (store), assim nas Tabelas 5.7, 5.8 e 5.9 são apresentandas as melhores configurações para as GPUs que foram usadas para os experimentos. Tabela 5.7. Sincos métrica gst_efficiency GPU-TitanX N Kernel gx gy gz bx by bz gst_efficiency % % % % % % % % % % Assim como para a métrica gld_efficiency, a métrica gst_efficiency retornou valores interessantes. Para todos os Ns nas GPUs TitanX e GTX1070 foram retornados o máximo de uso da memória global (store) com valores em 100%, contudo para a GPU GTX-780 houve variações para os valores de N = 64, 96, 128, 160, essa variação pode ser explicada pelo tamanho menor de memória da GPU GTX-780 comparada com as GPUs TitanX e GTX Outro dado interessante é quanto mais independência da memória, isto é, quanto mais se retirar dependência de laços, melhor será o uso da memória global (store) Cheng et al. (2014), isso explica porque com o kernel_0 obteve-se os melhores resultados, sendo que os laços mais internos foram transferidos para o arranjo de threads.

70 68 Tabela 5.8. Sincos métrica gst_efficiency GPU-GTX-1070 N Kernel gx gy gz bx by bz gst_efficiency % % % % % % % % % Tabela 5.9. Sincos métrica gst_efficiency GPU-GTX-780 N Kernel gx gy gz bx by bz gst_efficiency % % % % % % % % % % Resultados do algoritmo Sincos com a métrica inst_per_warp A métrica inst_per_warp mostra as instruções executadas por warp em cada multiprocessador. Altas variações nestes valores indicam cargas de trabalhos não uniformes para os blocos de grids do kernel. Segundo Cheng et al. (2014), embora tal desequilíbrio não tenha necessariamente que resultar em um baixo desempenho, a métrica instructions per warp é útil para verificar variações na atividade do SM. O padrão de código mais comum para causar grandes variações nas instructions per warp é blocos de código que são condicionalmente executados, nos quais a expressão condicional depende do valor anterior. Ainda de acordo com o que foi explicado por Cheng et al. (2014), as configurações que alcançaram valores próximos a 100 são ideais, por exemplo, para todas as configurações apresentadas na Tabela 5.10 e 5.11 alcançaram valores 100 ou próximos a isso. Para a GPU GTX-780 (Tabela 5.12), houve valores que extrapolaram o limite questionado por Cheng et al. (2014), por exemplo o menor uso de instruções para a GTX-780 foi a configuração (4, 1, 32, 2, 8, 8) para N = 128 com o valor de 95 instruções por warp. Para valores como N = 320 alcançou 22746, muito acima do normal. Questões arquiteturais como por exemplo a GPU GTX-780 ser da arquitetura Kepler pode ser um fator, bem como

71 69 Tabela Sincos métrica inst_per_warp GPU-TitanX N Kernel gx gy gz bx by bz inst_per_warp Tabela Sincos métrica inst_per_warp GPU-GTX-1070 N Kernel gx gy gz bx by bz inst_per_warp Tabela Sincos métrica inst_per_warp GPU-GTX-780 N Kernel gx gy gz bx by bz inst_per_warp tamanho de memória comparado as outras arquiteturas. Resultados do algoritmo Sincos com a métrica ipc A métrica ipc mostra o número máximo de instruções executadas em um único ciclo. Desta forma, números mais altos, isto é, próximo a 1.0 significam uso mais eficiente da GPU que é utilizada NVIDIA (2015b). Contudo, valores acima de 1.0 significam que há algum overclock e valores acima de 2.0 está com algum erro que faz o aumento do uso de instruções por ciclo.

72 70 Na Figura 5.1 são apresentadas estatísticas do número de instruções por ciclo em uma GPU, os valores considerados issued são instruções emitidas e executed são instruções executadas. Figura 5.1. Estatísticas de uso de instruções por ciclo. Fonte: (NVIDIA, 2015b) Tabela Sincos métrica ipc GPU-TitanX N Kernel gx gy gz bx by bz ipc Seguindo o especificado por NVIDIA (2015b), para todos os N s houve valores próximos a 1.0 e alguns obteve-se valores superiores a 1.0, por exemplo, na Tabela 5.15 obteve-se o valor de instruções por ciclo de clock. Para esta métrica, foi verificado vários valores de instruções por ciclo de clock, por exemplo, para a GPU TitanX com N = 128 as configurações (2, 2, 4, 2, 256, 2) houve instruções por ciclo de clock, variando somente os blocos (2, 2, 4, 4, 64, 4), (2, 2, 4, 1, 64, 16) e (2, 2, 4, 128, 2, 4) atingiu valores próximos como , e Comparando-se com a configuração que registrou o maior número de instruções por

73 71 Tabela Sincos métrica ipc GPU-GTX-1070 N Kernel gx gy gz bx by bz ipc Tabela Sincos métrica ipc GPU-GTX-780 N Kernel gx gy gz bx by bz ipc ciclo na GPU TitanX, a configuração (6, 54, 1, 4, 2, 32) para N = 288, registrou-se o valor de Realizando a mudança primeiramente nos grids (2, 3, 54, 4, 2, 32) e (4, 27, 3, 4, 2, 32) obtiveram-se os valores de e Quando a mudança ocorreu para as dimensões dos blocos (6, 54, 1, 2, 4, 32), (6, 54, 1, 2, 8, 16) e (6, 54, 1, 2, 16, 8), os valores também ficaram próximos a 1.12, ( , , ), deixando claro que os grids interferem no número de instruções por ciclo. Resultados do algoritmo Sincos com a métrica achieved_occupancy Para Cheng et al. (2014), as instruções são executadas sequencialmente dentro de cada núcleo CUDA. Quando um warp trava, o SM passa a executar outros warps elegíveis. Idealmente é importante ter warps suficientes para manter os núcleos do dispositivo ocupado, desta forma, ocupação é a razão de warps ativos para o número máximo de warps por SM. Para melhorar a ocupação, Cheng et al. (2014) especifica que é preciso redimensionar a configuração do bloco de threads ou reajustar os recursos para permitir mais warps ativos simultaneamente e então melhorar a utilização dos recursos de computação. Contudo, para o primeiro caso de redimensionar a configuração pode gerar extremos que restringem a utilização do dispositivo que são:

74 72 Tabela Sincos métrica achieved_occupancy GPU-TitanX N Kernel gx gy gz bx by bz achieved_occupancy Tabela Sincos métrica achieved_occupancy GPU-GTX-1070 N Kernel gx gy gz bx by bz achieved_occupancy Tabela Sincos métrica achieved_occupancy GPU-GTX-780 N Kernel gx gy gz bx by bz achieved_occupancy Pequenos blocos de threads: poucos threads por bloco levam a limites de hardware no número de warps por SM a serem atingidos antes que todos os recursos sejam totalmente utilizados. Blocos de threads grandes: muito threads por bloco levam a menos recursos de hardware por SM disponíveis para cada thread. Assim, os resultados podem ser visualizados nas Tabelas 5.16, 5.17 e 5.18, sendo

75 73 que, os valores para a ocupância próximos a 1.0 são configurações que comprovam o conceito apresentado por Cheng et al. (2014). Ainda nas Tabelas 5.16, 5.17 e 5.18, outro dado interessante que pode ser visualizado é que há, pelo menos um valor múltiplo de 32 nos blocos. Por exemplo, para N = 320 e N = 352 das GPUS TitanX e GTX-1070 os blocos possuem pelo menos um valor múltiplo de 32, e foram os valores que se aproximaram de 1.0, esses valores podem mostrar uma possível tendência de configuração. Resultados do algoritmo Sincos com a métrica sm_efficiency A métrica sm_efficiency verifica a porcentagem de warps ativos durante a execução do código, sua diferença para a métrica occupancy é que observa o quanto de warps estão ativos no momento, quanto mais próximo de 100% melhor é sua eficiência bem como a ocupação que o código está se aplicando nos núcleos CUDA. Tabela Sincos métrica sm_efficiency GPU-TitanX N Kernel gx gy gz bx by bz sm_efficiency % % % % % % % % % Tabela Sincos métrica sm_efficiency GPU-GTX-1070 N Kernel gx gy gz bx by bz sm_efficiency % % % % % % % % % % Para os valores apresentados nas Tabelas 5.19, 5.20 e 5.21, todas as configurações alcançaram valores superiores a 90%, vale ressaltar que esses são consideradas as melhores configurações. Por exemplo, para o dispositivo GTX-1070 considerando-se N = 256 houveram

76 74 Tabela Sincos métrica sm_efficiency GPU-GTX-780 N Kernel gx gy gz bx by bz sm_efficiency % % % % % % % % % configurações que obtiveram valores como 68.1% para a configuração (16, 2, 2, 32, 32, 1) e 73.79% para a configuração (128, 4, 4, 1, 16, 2). Também pode ser visto que foi a única métrica que os melhores resultados foram encontrados com o kernel_1, sendo ele as interações dos dois laços mais internos transferidos para o arranjo de threads (Tabela 3.3). Os valores dos blocos muito próximos (valores entre 2, 4 e 8), como exemplo as configurações (1, 2, 784, 2, 4, 4, 24, 4, 6, 4, 8, 2 e 32, 16, 1, 1, 4, 8) dos dispositivos TitanX, GTX-1070 e GTX-780, respectivamente, demonstram que pode haver uma possível tendência de configuração Experimento 2 com OpenTuner: GEMM As seções seguintes apresentam os resultados obtidos pelo OpenTuner para o algoritmo GEMM com as métricas gld_efficiency, gst_efficiency, inst_per_warp, ipc, achieved_occupancy e sm_efficiency. Os resultados completos para este experimento podem ser visualizados no repositório do GitHub 11, sendo que, todas as configurações testadas foram armazenadas em arquivos no formato.csv e os melhores resultados foram armazenados em arquivos no formato.json. Resultados do algoritmo GEMM com a métrica gld_efficiency A métrica gld_efficiency que verifica a taxa de transferência solicitada da memória global para a taxa de transferência da memória global necessária, as Tabelas 5.22, 5.23 e 5.24 expõe as melhores configurações encontradas pelo OpenTuner. Os valores para a métrica gld_efficiency não retornaram valores satisfatórios como o algoritmo GEMM, os maiores valores encontrados ficaram em torno de 20%. Uma possível resposta para esses valores é o nível de exigência computacional que o algoritmo representa, bem como o kernel ter somente um laço transferido para o arranjo de threads. 11 <

77 75 Tabela GEMM métrica gld_efficiency GPU-TitanX N gx gy gz bx by bz gld_efficiency % % % % % % % % Tabela GEMM métrica gld_efficiency GPU-GTX-1070 N gx gy gz bx by bz gld_efficiency % % % % % % % % Tabela GEMM métrica gld_efficiency GPU-GTX-780 N gx gy gz bx by bz gld_efficiency % % % % % % % % Um dado interessante que pode ser encontrado no arquivo contendo todas as configurações geradas (GitHub 12 ) é que por exemplo, para N = 96 no dispositivo GTX-1070 em torno de 80% dos testes retornaram o valor de 21.63%, sendo apenas 11 configurações com o valor de 21.67%. Interessante também é que para o valor de N = 512 na GPU GTX-780 todas as configurações tiveram o valor de 21.7%. Os gráficos na Figura 5.2 apresentam uma visão geral das configurações (eixo X) com os valores da métrica gld_efficiency. Para a GPU TitanX para N = 160 foi de 21.69%, sendo que os menores valores ficaram na faixa de 19%, essa diferença pode ser visualizada no gráfico (a) da Figura 5.2, na qual aparece 3 faixas de valores aglomerados, sendo a primeira em torno de 19%, a segunda 12 <

78 76 (a) Espaço de busca métrica gld_efficiency TitanX (b) Espaço de busca métrica gld_efficiency GTX-1070 (c) Espaço de busca métrica gld_efficiency GTX-780 Figura 5.2. Comparação entre os testes e os valores encontrados para a métrica gld_efficiency faixa em torno de 20% e por último a faixa entre 21.5%, sendo esta com os maiores valores. Essa divisão fica mais nítida visualizada nos gráficos (b) e (c) que são respectivamente dos dispositivos GTX-1070 e GTX-780 para N = 512 e 320.

79 77 Resultados do algoritmo GEMM com a métrica gst_efficiency Como foi feito com a métrica gld_efficiency foi realizada a execução dos testes para a métrica gst_efficiency, esta retorna a porcentagem de uso da memória global em operações de store. As Tabelas 5.25, 5.26 e 5.27 trazem um demonstrativo das melhores configurações para a métrica gst_efficiency. Tabela GEMM métrica gst_efficiency GPU-TitanX N gx gy gz bx by bz gst_efficiency % % % % % % % % Tabela GEMM métrica gst_efficiency GPU-GTX-1070 N gx gy gz bx by bz gst_efficiency % % % % % % % % Assim como a métrica gld_efficiency a métrica gst_efficiency não trouxe valores satisfatórios, tendo como máximo a porcentagem entre 12.2% a 12.5%. Entre todos os testes realizados os valores ficaram entre 6.69% a 12.5%, somado todas configurações testadas nos três dispositivos, tem-se que 98% das configurações apresentaram o valor 12.5%. Assim para o algoritmo GEMM qualquer configuração que for escolhida trará um valor próximo de 12.5%. Resultados do algoritmo GEMM com a métrica inst_per_warp A métrica inst_per_warp representa as instruções executadas por warp em cada multiprocessador. As Tabelas 5.28, 5.29 e 5.30 expõem as configurações e os resultados gerados pela métrica inst_per_warp. Esta foi a métrica que trouxe mais curiosidade para o desenvolvimento deste trabalho. Como citado nos resultados pela métrica inst_per_warp no algoritmo sincos, os valores considerados bons são próximos a 100.0, contudo, os valores apresentados por esta métrica para

80 78 Tabela GEMM métrica gst_efficiency GPU-GTX-780 N gx gy gz bx by bz gst_efficiency % % % % % % % % Tabela GEMM métrica inst_per_warp GPU-TitanX N gx gy gz bx by bz inst_per_warp Tabela GEMM métrica inst_per_warp GPU-GTX-1070 N gx gy gz bx by bz inst_per_warp Tabela GEMM métrica inst_per_warp GPU-GTX-780 N gx gy gz bx by bz inst_per_warp o algoritmo GEMM foram muito alto, fazendo com que verificássemos todo o funcionamento do OpenTuner e a verificação se o código do algoritmo GEMM estava correto.

81 79 Após uma análise dos códigos e aprofundamento do estudo desta métrica, foi descoberto uma dependência na implementação do kernel em CUDA, na qual o valor da matriz C depende das multiplicações da matriz A e B, acarretando um grande número de instruções por warp. Porém, para alguns valores de N, por exemplo, N = 320 em execuções no dispositivo GTX-780 obteve-se valores próximos a 100. Resultados do algoritmo GEMM com a métrica ipc Após a execução da métrica inst_per_warp foi executado os testes para a métrica ipc que mostra o número máximo de instruções executadas por ciclo de clock. Nas Tabelas 5.31, 5.32 e 5.33 são apresentadas as configurações encontradas utilizando-se a métrica ipc. Tabela GEMM métrica ipc GPU-TitanX N gx gy gz bx by bz ipc Tabela GEMM métrica ipc GPU-GTX-1070 N gx gy gz bx by bz ipc Para a métrica ipc há uma divergência com a métrica inst_per_warp, sendo que na métrica anterior os valores extrapolaram o limite, nesta métrica os valores ficaram abaixo do esperado, sendo o maior valor para a configuração (160, 10, 2, 1, 32, 1) e para N = 320. Os valores testados ficaram entre 0.05 e 0.15, valores baixos. Resultados do algoritmo GEMM com a métrica achieved_occupancy A métrica achieved_occupancy apresenta a razão de warps ativos para o número máximo de warps por SM. As Tabelas 5.34 e 5.35 apresentam, respectivamente, as melhores configurações

82 80 Tabela GEMM métrica ipc GPU-GTX-780 N gx gy gz bx by bz ipc para a métrica achieved_occupancy para os dispositivos TitanX e GTX-780. Tabela GEMM métrica occupancy GPU-TitanX N gx gy gz bx by bz occupancy Tabela GEMM métrica occupancy GPU-GTX-780 N gx gy gz bx by bz occupancy Com a métrica achieved_occupancy e sm_efficiency ficou confirmado os baixos valores mesmo realizando as modificações nas configurações. Foi pesquisado na literatura e um dos motivos para esses valores baixos é pelo kernel ter somente um laço transferido para arranjos de threads fazendo com que use o mínimo da GPU. O que pode ser exemplificado a partir de N = 320, onde há um aumento no uso dos warps e no valor de N = 512 há um aumento significativo do uso dos warps. Se houvesse uma continuação no aumento dos valores de N, haveria um acréscimo no uso dos warps no dispositivo. Para exemplificarmos este aumento, foi realizado um teste por

83 81 força-bruta na GPU TitanX com os valor de N = 1024, a Seção mostra os resultados obtidos. Para entender esta diferença foi desenvolvido dois diagramas de caixas que apresenta o mínimo, média e os valores máximos alcançados para a métrica achieved_occupancy com N = 512 (Figura 5.3). (a) Variação de valores na métrica achieved_occupancy TitanX (b) Variação de valores na métrica achieved_occupancy GTX-780 Figura 5.3. Variação entre as configurações e a métrica achieved_occupancy para os dispositivos TitanX e GTX-780 Na GPU TitanX o diagrama de caixas mostra primeiramente no extremo o menor valor encontrado pela métrica utilizada, sendo este valor , pesquisando no arquivo que contém todos os testes, a configuração que teve este desempenho foi (128, 1, 64, 4, 1, 8),com mediana em e com máximo em com a seguinte configuração (256, 2, 1, 1, 8, 64).

84 82 Na GPU GTX-780 as configurações obtiveram resultados satisfatórios, sendo que o mínimo encontrado no dispositivo ( ) é comparável a mediana encontrada pela GPU TitanX, bem como sua mediana encontra-se maior que a a melhor configuração do dispositivo Titanx. A maior taxa de ocupação encontrada foi de Uma possível resposta para esta diferença de valores entre os dois dispositivos é arquitetura, sendo a GPU GTX-780 arquitetura Kepler com menor número de núcleos e memória comparado a GPU Titanx. Resultados do algoritmo GEMM com a métrica sm_efficiency Comparável à métrica achieved_occupancy, a métrica sm_efficiency traz a porcentagem de warps ativos durante a execução do código. As Tabelas 5.36, 5.37 e 5.38 apresentam os melhores valores para a métrica sm_efficiency. Tabela GEMM métrica sm_efficiency GPU-TitanX N gx gy gz bx by bz sm_efficiency % % % % % % % % Tabela GEMM métrica sm_efficiency GPU-GTX-1070 N gx gy gz bx by bz sm_efficiency % % % % % % % % Com a métrica sm_efficiency fica evidente que com o aumento do N há um maior uso dos warps do dispositivo, bem como, esse aumento é explicado pela simplicidade do kernel. Essa simplicidade é demonstrado ainda mais na GPU GTX-780 que possui menor número de núcleos CUDA do que os outros dois dispositivos, para N = 512 obteve-se o valor de 98.95%. A partir deste aumento foi realizado testes por força-bruta para o algoritmo GEMM para validar se realmente os valores são iguais para a métrica sm_efficiency. O resultado

85 83 será apresentado na Seção Tabela GEMM métrica sm_efficiency GPU-GTX-780 N gx gy gz bx by bz sm_efficiency % % % % % % % % Para uma melhor visualização desta diferença na métrica sm_efficiency, foi criado diagrama de caixas para N = 512 para as três GPUS, na Figura 5.4 é mostrado os três diagramas de caixa. O primeiro diagrama de caixas Figura 5.4 (a) mostra as variações que ocorreram para a encontrar a melhor configuração para o dispositivo Titanx. As variações ocorreram entre o mínimo (4.69%), tendo sua mediana o valor de 11.26% e o máximo entre 59.18%. Para a GPU GTX-1070 (Figura 5.4 (b)) teve variações entre o mínimo que foi encontrado de 4.69% a mediana em torno de 11.22% e máximo de 59.09%. Por fim a Figura 5.4 (c) apresenta as variações que ocorreram na GPU GTX-780, havendo variações entre 9.3%, com mediana em 19.15% e máximo estipulado em 98.95%. Identificou-se uma diferença a partir dos diagramas de caixas entre os valores encontrados entre as métricas achieved_occupancy (Figura 5.3) e a métrica sm_efficiency (Figura 5.4), sendo que na primeira o corpo do diagrama de caixas esta mais próximo da solução ótima, já para a segunda métrica os valores ficaram próximos a solução mínima, sendo que a melhor configuração longe do "corpo"do diagrama de caixas. Com esta comparação fica mais evidente a dificuldade em encontrar uma configuração que utilize os recursos do dispositivo acelerador com mais eficiência Experimento 3: Experimento força-bruta GEMM Com os resultados para o algoritmo GEMM abaixo do esperado, foi realizado uma pesquisa para verificar se havia erros nos testes na ferramenta de autotuning ou erro na implementação do algoritmo na plataforma CUDA, assim desenvolveu-se um teste por força bruta para o algoritmo GEMM para validar o funcionamento correto do OpenTuner. Esse teste por força-bruta foi desenvolvido na linguagem Python, na qual recebia um arquivo com todas as configurações válidas, assim ao invés de realizar os cortes através por algoritmos de busca, ele realizava o teste com todas as configurações. O Código 5.3 apresenta a função manipulator que realiza os testes. O restante do código está disponível do

86 84 (a) Variação de valores na métrica sm_efficiency TitanX (b) Variação de valores na métrica sm_efficiency GTX-1070 (c) Variações de valores na métrica sm_efficiency GTX-780 Figura 5.4. Variação entre as configurações e a métrica achieved_occupancy para os dispositivos TitanX e GTX-780

87 85 repositório do GitHub def manipulador ( ) : 3 l i s t _ c o n f i g s = r e a d _ f i l e _ c o n f i g s ( ) 4 count = 1 5 for v a l o r in l i s t _ c o n f i g s : 6 print s t r ( count ) + " de 21231" 7 p = subprocess. Popen ( nvprof --metrics sm_efficiency./gemm -cuda.exe 0 +str ( v a l o r ), s h e l l=true, stdout=subprocess. PIPE, s t d e r r= subprocess.stdout) 8 for l i n e in p. stdout. r e a d l i n e s ( ) : 9 r e s u l t a d o = l i n e, 10 r e t v a l = p. wait ( ) 11 s t r g = "" + l i n e 12 i f s t r g. f i n d ( "Multiprocessor Activity" ) > 1: 13 idx = s t r g. index ( "Multiprocessor Activity" ) 14 s u b s r t g = s t r g [ idx : ]. s p l i t ( " " ) 15 #p r i n t " s u b s t r g : ", s u b s r t g 16 s u b s t r i n g = s u b s r t g [ 3 ] 17 s u b s t r i n g 1 = s u b s t r i n g. r e p l a c e ( "%", ) 18 metric_value = f loat ( s u b s t r i n g 1 ) 19 print "sm_efficiency: ", metric_value 20 v a l o r = v a l o r. r e p l a c e ( " ", "," ). r e p l a c e ( ",512,512,512", "" ) 21 arquivo_csv = open( "gemm -smefficiency -512-todas -conf -funcid -titanx.csv ", "a" ) 22 arquivo_csv. w r i t e ( "gx,gy,gz,bx,by,bz,funcid,smefficiency \n" ) 23 arquivo_csv. w r i t e ( str ( v a l o r )+","+str ( metric_value )+"\n" ) 24 count += Código 5.3. Parte do código que realiza teste por força bruta para o algoritmo GEMM. Por este método ser demorado, foi realizado somente o teste para a métrica sm_efficiency com N = 512 para as GPUs TitanX e GTX-780. Também para verificar o conceito de que quanto maior o N maior o uso de warps foi executado para o tamanho de N = 1024 para o dispositivo TitanX. A Tabela 5.39 apresenta as melhores configurações encontradas por força-bruta para a métrica sm_efficiency. 13 < configuracoes/script_tuning.py>

88 86 Tabela Melhores configurações por força-bruta para a métrica sm_efficiency N GTX-780 TitanX Configuração sm_efficiency Configuração sm_efficiency 512 (4,2,1024,32,1,1) 98.87% (1,1,8192,32,1,1) 58.32% 1024 (1,8192,4,32,1,1) 97.54% Como esse experimento foi realizado para validar a ferramenta OpenTuner, foi comparado primeiramente os resultados da GPU GTX-780. Com o uso da ferramenta OpenTuner o valor encontrado para N = 512 foi de 98.95% (Tabela 5.38), valor superior ao encontrado pelo método de força-bruta que foi de 98.87%, uma diferença de 0.08%. Outro detalhe interessante são as configurações, sendo que para o teste com o OpenTuner a melhor configuração foi (512, 1, 16, 1, 32, 1), já para o método de força bruta foi a melhor configuração foi (4, 2, 1024, 32, 1, 1), ambas configurações com mais grids por bloco, motivando a ideia que há uma tendência de configuração. A comparação com o dispositivo TitanX mostra que maior grids por bloco é uma boa tendência de configuração para a métrica sm_efficiency sendo que a melhor configuração pela execução do OpenTuner. Também para uma melhor visualização das configurações executadas, a Tabela 5.40 mostra as configurações que obteve menores resultados juntamente com as configurações que possuíam maiores resultados. No GitHub 14 há os arquivos contendo todas as configurações executadas. Visualizando a Tabela 5.40, pode se perceber que não há uma diferença de configurações entre as configurações que obtiveram menor desempenho equiparado com as que obtiveram maiores desempenho. Para as configurações que tiveram maiores valores para a métrica sm_efficiency tanto para a GPU GTX-780 e TitanX tiveram como configuração de blocos os valores de (32, 1, 1), bem como mais threads por grids, mostrando que pode haver uma tendência de configuração que deve ser estudada. Para validar a ideia que com o aumento do valor de N para o algoritmo GEMM aumentaria o uso dos warps, assim, a Tabela 5.41 mostra as configurações e as porcentagens obtidas para a métrica sm_efficiency na GPU Titanx. A Tabela 5.41 pode ser comparada com a Tabela 5.40 no que diz respeito aos valores de blocos. Por exemplo, para blocos com valor de (2, 512, 1) os testes obtiveram valores entre 5 a 6%, entretanto, para valores de blocos (32, 1, 1) obtiveram os maiores valores para N = Já na Tabela 5.36 o resultado para N = 512 foi de 59%, afirmando que para o algoritmo GEMM quanto maior o valor de N há um aumento na utilização dos warps. Outra possível tendência encontrada é que os valores de blocos interferem na métrica sm_efficiency 14 < configuracoes>

89 87 Tabela Dez Maiores e dez menores valores com base na métrica sm_efficiency N GTX-780 TitanX Configuração sm_efficiency Configuração sm_efficiency ,1,4,16,8, % 16,16,1,1024,1,1 5.66% ,2,2,16,8, % 32,1,8,1024,1,1 5.66% ,4,1,16,8, % 32,2,4,1024,1,1 5.66% ,1,2,16,8, % 32,4,2,1024,1,1 5.66% ,2,1,16,8, % 32,8,1,1024,1,1 5.66% ,1,1,16,8, % 1,4,512,32,4,1 5.66% 512 1,1,256,32,8, % 1,8,256,32,4,1 5.67% 512 1,2,128,32,8, % 1,16,128,32,4,1 5.66% 512 1,4,64,32,8, % 1,32,64,32,4,1 5.67% 512 1,8,32,32,8, % 1,64,32,32,4,1 5.66% 512 1,1,8192,32,1, % 1,1,8192,32,1, % 512 1,2,4096,32,1, % 1,2,4096,32,1, % 512 1,4,2048,32,1, % 1,4,2048,32,1, % 512 1,8,1024,32,1, % 1,8,1024,32,1, % 512 1,16,512,32,1, % 1,16,512,32,1,1 58.2% 512 1,32,256,32,1, % 1,32,256,32,1, % 512 1,64,128,32,1, % 1,64,128,32,1, % 512 1,128,64,32,1, % 1,128,64,32,1,1 58.1% 512 1,256,32,32,1, % 1,256,32,32,1, % 512 1,512,16,32,1, % 1,512,16,32,1, % Tabela Parte dos testes realizado por força-bruta para a métrica sm_efficiency N TitanX Configuração sm_efficiency ,16,1,2,512,1 5.66% ,1,8,2,512,1 5.66% ,2,4,2,512,1 5.66% ,4,2,2,512,1 5.66% ,8,1,2,512,1 5.66% ,2,8192,2,32,1 61.2% ,4,4096,2,32, % ,8,2048,2,32, % ,16,1024,2,32, % ,32,512,2,32, % ,128,128,32,1, % ,1,4096,32,1, % ,512,8,32,1, % ,4,512,32,1, % ,1,256,32,1, %

90 Experimento 4: Experimento força-bruta Sincos Também para validar os valores encontrados pela ferramenta OpenTuner, foi realizado o teste em força-bruta para todas as configurações válidas para o algoritmo sincos com N = 320 e a métrica sm_efficiency. A primeira dificuldade encontrada foi o tempo de processamento de todas as configurações, uma vez que para N = 320 são geradas 33 mil configurações possíveis, e como seria necessário executar nos 4 kernels, houve então a quantia de 132 mil execuções de testes, bem a mais do que a ferramenta que encontrou a melhor configuração com 5 mil testes. Para a execução deste teste de força bruta a execução demorou em torno de 2 dias para concluir, e com o uso da ferramenta de autotuning a melhor configuração foi encontrada em torno de 4 horas, mostrando que a ferramenta é uma solução para encontrar as melhores configurações. A Tabela 5.42 apresenta os menores valores encontrados, os valores considerados médios e as configurações que retornaram o melhor uso da eficiência dos warps. Tabela Teste por força-bruta métrica sm_efficiency N TitanX Kernel Configuração sm_efficiency ,1,8,8,4,2 50.8% ,32,1,2,16, % ,20,16,1,16, % ,10,2,64,4, % ,1,10,64,4, % ,10,1,1,8, % ,1,64,4,2,4 99.1% ,400,1,1,8, % ,100,1,1,8, % ,40,1,1,8, % Comparou-se os valores encontrados na força-bruta e pela ferramenta de autotuning. Utilizando a força-bruta foi encontrada a configuração (1.1600, 2, 1, 1, 8, 4) com eficiência de warps em 99.47%. Com o uso do OpenTuner foi encontrado a configuração (2, 160, 10, 4, 2, 4) com eficiência de exatamente 99.47%, efetivando que o uso da ferramenta de autotuning encontra as melhores configurações de uma maneira rápida e correta.

91 Resultados obtidos para Detecção e Tendências de Configuração Com os testes concluídos, os arquivos gerados com as configurações foram utilizados na tentativa de detectar um padrão ou uma tendência de configuração que pudesse influenciar no desempenho. No tratamento dos dados para a detecção de um possível padrão utilizou-se a linguagem de programação R. As Figuras 5.5, 5.6 e 5.7 apresentam uma primeira análise de dados e as dificuldades encontradas para detectar o padrão. Figura 5.5. Valores encontrados segundo a métrica sm_efficiency A Figura 5.5 mostra que para as configurações com a métrica sm_efficiency há 4 intervalos de eficiência usando como base os grids (eixo x), esses intervalos estão entre 20%, 40%, 80% e próximos a 100%. Como estamos a procura dos melhores resultados foi feito uma análise entre os intervalos de 80% a 100%, o resultado é visto na Figura 5.6. A partir desta análise entre intervalo de % foi verificado um "pico"de valores entre 98% a 99%, sendo estes valores próximos. Desta forma, foi criado um novo conjunto de dados com somente os valores entre esse intervalos de 98 a 99%, a Figura 5.7 mostra uma tabela gráfica de todas as melhores configurações, sendo os grids apresentados no eixo x, na parte superior os blocos e no eixo y o resultado que a combinação entre grids e blocos encontrou. A partir deste gráfico vimos a complexidade em detectar um padrão visto que os valores de grids e blocos não seguiam um valor constante. Para verificar essa constante, populamos um banco de dados contendo todas as execuções, na qual a partir de queries realizamos comparações entre grids e blocos, blocos com blocos e grids com grids, por exemplo, para comparar grids e blocos, era escolhido uma configuração de grid (gx, gy, gz) e esta procurava todos os resultados com esta configuração e com valores de blocos diferentes. Foi necessário o uso de uma ferramenta de banco de

92 90 Figura 5.6. Densidade de valores entre 80% a 100% dados, visto que, havia muitas configurações, por exemplo para comparação entre grids e blocos havia um milhão cento e setenta e três mil novecentas e noventa e duas ( ) comparações. No GitHub 15 pode-se visualizar o arquivo contendo as queries e o arquivo.csv contendo todos os resultados. As conclusões sobre o passo de detectar padrões é visto no Capítulo Considerações Finais Este capítulo apresentou os resultados obtidos com os experimentos que foram realizados. Primeiro foram apresentados os testes iniciais com o algoritmo soma de vetores que contextualizou que é uma dificuldade encontrar uma configuração correta escolhendo valores aleatoriamente, e os testes foram repetidos com o algoritmo sincos. Em seguida, foram apresentados os resultados dos testes com o uso da ferramenta de autotuning OpenTuner. E por fim, apresentou-se as dificuldades de se encontrar um padrão ou tendência de configuração. 15 <

93 Figura 5.7. Melhores resultados para a métrica sm_efficiency 91

5 Unidades de Processamento Gráfico GPUs

5 Unidades de Processamento Gráfico GPUs 5 Unidades de Processamento Gráfico GPUs As GPUs são processadores maciçamente paralelos, com múltiplos elementos de processamento, tipicamente utilizadas como aceleradores de computação. Elas fornecem

Leia mais

Computação Paralela (CUDA)

Computação Paralela (CUDA) Universidade Federal do Amazonas Faculdade de Tecnologia Departamento de Eletrônica e Computação Computação Paralela (CUDA) Hussama Ibrahim hussamaibrahim@ufam.edu.br Notas de Aula Baseado nas Notas de

Leia mais

Arquiteturas de Computadores

Arquiteturas de Computadores Arquiteturas de Computadores Computadores vetoriais Fontes dos slides: Livro Patterson e Hennessy, Quantitative Approach e site do curso EE 7722, GPU Microarchitecture do Prof. David Koppelman Graphical

Leia mais

3 Computação de Propósito Geral em Unidades de Processamento Gráfico

3 Computação de Propósito Geral em Unidades de Processamento Gráfico 3 Computação de Propósito Geral em Unidades de Processamento Gráfico As Unidades de Processamento Gráfico (GPUs) foram originalmente desenvolvidas para o processamento de gráficos e eram difíceis de programar.

Leia mais

Introdução à Programação Paralela através de Padrões. Denise Stringhini Calebe Bianchini Luciano Silva

Introdução à Programação Paralela através de Padrões. Denise Stringhini Calebe Bianchini Luciano Silva Introdução à Programação Paralela através de Padrões Denise Stringhini Calebe Bianchini Luciano Silva Sumário Introdução: conceitos de paralelismo Conceitos básicos sobre padrões de programação paralela

Leia mais

Comparação de eficiência entre OpenCL e CUDA

Comparação de eficiência entre OpenCL e CUDA Aluno: Thiago de Gouveia Nunes Orientador: Prof. Marcel P. Jackowski GPGPU O que é GPGPU? É programação de propósito geral em GPUs. =D GPGPU Existem 2 linguagens populares no mercado para GPGPU, o CUDA

Leia mais

Paralelismo de dados. (execução de simultaneidade) Tipo de arquitetura paralela SIMD. SIMD (Single Instruction Multiple Data)

Paralelismo de dados. (execução de simultaneidade) Tipo de arquitetura paralela SIMD. SIMD (Single Instruction Multiple Data) Paralelismo de dados (execução de simultaneidade) Em métodos tradicionais de programação (processamento sequencial), uma grande quantidade de dados é processada em um único núcleo de uma CPU, enquanto

Leia mais

Processamento de áudio em tempo real utilizando dispositivos não convencionais:

Processamento de áudio em tempo real utilizando dispositivos não convencionais: Processamento de áudio em tempo real utilizando dispositivos não convencionais: Processamento paralelo com Pure Data e GPU. André Jucovsky Bianchi ajb@ime.usp.br Departamento de Ciência da Computação Instituto

Leia mais

Paralelismo de dados. (execução de simultaneidade) Tipo de arquitetura paralela SIMD. SIMD (Single Instruction Multiple Data)

Paralelismo de dados. (execução de simultaneidade) Tipo de arquitetura paralela SIMD. SIMD (Single Instruction Multiple Data) Paralelismo de dados (execução de simultaneidade) Em métodos tradicionais de programação (processamento sequencial), uma grande quantidade de dados é processada em um único núcleo de uma CPU, enquanto

Leia mais

Eng. Thársis T. P. Souza

Eng. Thársis T. P. Souza Introdução à Computação de Alto Desempenho Utilizando GPU Seminário de Programação em GPGPU Eng. Thársis T. P. Souza t.souza@usp.br Instituto de Matemática e Estatística - Universidade de São Paulo Introdução

Leia mais

Bacharelado em Sistemas de Informação Sistemas Operacionais. Prof. Filipo Mór

Bacharelado em Sistemas de Informação Sistemas Operacionais. Prof. Filipo Mór Bacharelado em Sistemas de Informação Sistemas Operacionais Prof. Filipo Mór WWW.FILIPOMOR.COM - REVISÃO ARQUITETURAS PARALELAS Evolução das Arquiteturas Evolução das Arquiteturas Entrada CPU Saída von

Leia mais

Técnicas de Processamento Paralelo na Geração do Fractal de Mandelbrot

Técnicas de Processamento Paralelo na Geração do Fractal de Mandelbrot Técnicas de Processamento Paralelo na Geração do Fractal de Mandelbrot Bruno Pereira dos Santos Dany Sanchez Dominguez Esbel Tomás Evalero Orellana Universidade Estadual de Santa Cruz Roteiro Breve introdução

Leia mais

Sparse Matrix-Vector Multiplication on GPU: When Is Rows Reordering Worthwhile?

Sparse Matrix-Vector Multiplication on GPU: When Is Rows Reordering Worthwhile? Sparse Matrix-Vector Multiplication on GPU: When Is Rows Reordering Worthwhile? Paula Prata João Muranho Instituto de Telecomunicações Departamento de Informática Universidade da Beira Interior Instituto

Leia mais

Computadores e Programação (DCC/UFRJ)

Computadores e Programação (DCC/UFRJ) Computadores e Programação (DCC/UFRJ) Aula 3: 1 2 3 Abstrações do Sistema Operacional Memória virtual Abstração que dá a cada processo a ilusão de que ele possui uso exclusivo da memória principal Todo

Leia mais

Arquitetura de Computadores. Processamento Paralelo

Arquitetura de Computadores. Processamento Paralelo Arquitetura de Computadores Processamento Paralelo 1 Multiprogramação e Multiprocessamento Múltiplas organizações de computadores Single instruction, single data stream - SISD Single instruction, multiple

Leia mais

Arquiteturas paralelas Parte 1

Arquiteturas paralelas Parte 1 Arquiteturas paralelas Parte 1 Processamento Paralelo Prof. Oberlan Romão Departamento de Computação e Eletrônica DCEL Centro Universitário Norte do Espírito Santo CEUNES Universidade Federal do Espírito

Leia mais

Introdução ao CUDA. Material elaborado por Davi Conte.

Introdução ao CUDA. Material elaborado por Davi Conte. Introdução ao CUDA Material elaborado por Davi Conte. O objetivo deste material é que o aluno possa iniciar seus conhecimentos em programação paralela, entendendo a diferença da execução de forma sequencial

Leia mais

What is? Eduardo Viola Nicola Disciplina de IPPD

What is? Eduardo Viola Nicola Disciplina de IPPD What is? Eduardo Viola Nicola evnicola@inf.ufpel.edu.br Disciplina de IPPD Sumário 1)Introdução 2)Princípio Geral de Funcionamento 3)Exemplos de Aplicações 4)Modelo de Programação 5)Linguagens Suportadas

Leia mais

COMPUTAÇÃO PARALELA COM ACELERADORES GPGPU 1. Emilio Hoffmann De Oliveira 2, Edson Luiz Padoin 3.

COMPUTAÇÃO PARALELA COM ACELERADORES GPGPU 1. Emilio Hoffmann De Oliveira 2, Edson Luiz Padoin 3. COMPUTAÇÃO PARALELA COM ACELERADORES GPGPU 1 Emilio Hoffmann De Oliveira 2, Edson Luiz Padoin 3. 1 Trabalho de Conclusão de Curso 2 Aluno do Curso de Ciência da Computação - emiliohoffmann@hotmail.com

Leia mais

INE5645-Programação Paralela e Distribuída Aula 17/09/2018 Nome

INE5645-Programação Paralela e Distribuída Aula 17/09/2018 Nome INE5645-Programação Paralela e Distribuída Aula 17/09/2018 Nome Para paralelizar códigos de programas, tudo que necessitamos é de uma construção sintática denominada kernel. Seja o kernel: kernel void

Leia mais

Fabrício Gomes Vilasbôas

Fabrício Gomes Vilasbôas Fabrício Gomes Vilasbôas Apresentação Placas Arquitetura Toolkit e Ferramentas de Debug Pensando em CUDA Programação CUDA Python Programação PyCUDA 1) Grids( padrão Globus) 2) Clusters ( padrão MPI) 3)

Leia mais

1.1 Descrição do problema A programação genética (PG) é uma meta-heurística utilizada para gerar programas de computadores, de modo que o computador

1.1 Descrição do problema A programação genética (PG) é uma meta-heurística utilizada para gerar programas de computadores, de modo que o computador 1 Introdução 1.1 Descrição do problema A programação genética (PG) é uma meta-heurística utilizada para gerar programas de computadores, de modo que o computador possa resolver problemas de forma automática

Leia mais

PROCESSADORES Unidade de Controle Unidade Aritmética e Lógica efetua memória de alta velocidade registradores Program Counter Instruction Register

PROCESSADORES Unidade de Controle Unidade Aritmética e Lógica efetua memória de alta velocidade registradores Program Counter Instruction Register PROCESSADORES Um computador digital consiste em um sistema interconectado de processadores, memória e dispositivos de entrada e saída. A CPU é o cérebro do computador. Sua função é executar programas armazenados

Leia mais

Multiprogramação leve em arquiteturas multi-core

Multiprogramação leve em arquiteturas multi-core Multiprogramação leve em arquiteturas multi-core Prof. Dr. Departamento de Informática Universidade Federal de Pelotas Sumário Arquiteturas multi-core Programação multithread Ferramentas de programação

Leia mais

Organização e Arquitetura de Computadores I

Organização e Arquitetura de Computadores I Universidade Federal de Campina Grande Centro de Engenharia Elétrica e Informática Unidade Acadêmica de Sistemas e Computação Curso de Bacharelado em Ciência da Computação Organização e Arquitetura de

Leia mais

INE 5645 PROGRAMAÇÃO PARALELA E DISTRIBUIDA PROVA 2 13/11/2017 ALUNO

INE 5645 PROGRAMAÇÃO PARALELA E DISTRIBUIDA PROVA 2 13/11/2017 ALUNO INE 5645 PROGRAMAÇÃO PARALELA E DISTRIBUIDA PROVA 2 13/11/2017 ALUNO 1. Sockets - Indicar (Verdade/Falso): (2.0) (a) (Verdade/Falso) A comunicação entre processos consiste em transmitir uma mensagem entre

Leia mais

Microprocessadores II - ELE 1084

Microprocessadores II - ELE 1084 Microprocessadores II - ELE 1084 CAPÍTULO III PROCESSADORES P5 3.1 Gerações de Processadores 3.1 Gerações de Processadores Quinta Geração (P5) Pentium (586) 32 bits; Instruções MMX; Concorrente K5 (AMD).

Leia mais

ARQUITETURA DE COMPUTADORES

ARQUITETURA DE COMPUTADORES RCM00014 Haswell wafer ARQUITETURA DE COMPUTADORES Prof. Luciano Bertini Site: http://www.professores.uff.br/lbertini/ Objetivos do Curso Entendimento mais aprofundado do funcionamento

Leia mais

AULA 03: FUNCIONAMENTO DE UM COMPUTADOR

AULA 03: FUNCIONAMENTO DE UM COMPUTADOR ORGANIZAÇÃO E ARQUITETURA DE COMPUTADORES I AULA 03: FUNCIONAMENTO DE UM COMPUTADOR Prof. Max Santana Rolemberg Farias max.santana@univasf.edu.br Colegiado de Engenharia de Computação O QUE É UM COMPUTADOR?

Leia mais

ORGANIZAÇÃO DE COMPUTADORES

ORGANIZAÇÃO DE COMPUTADORES ORGANIZAÇÃO DE COMPUTADORES TECNOLOGIAS EM REDES DE COMPUTADORES Semestre 2015.2 Prof. Dsc. Jean Galdino As principais arquiteturas de processadores são: Von Neumann; Harvard. ARQUITETURAS AULA 06 28/10/2015

Leia mais

Organização de Computadores I

Organização de Computadores I Organização de Computadores I Aula 2 Material: Diego Passos http://www.ic.uff.br/~debora/orgcomp/pdf/parte2.pdf Organização de Computadores I Aula 2 1/29 Tópicos de Computação. de um Sistema de Computação..

Leia mais

Memory-level and Thread-level Parallelism Aware GPU Architecture Performance Analytical Model

Memory-level and Thread-level Parallelism Aware GPU Architecture Performance Analytical Model Memory-level and Thread-level Parallelism Aware GPU Architecture Performance Analytical Model Sunpyo Hong Hyesoon Kim ECE School of Computer Science Georgia Institute of Technology April 6, 2011 Visão

Leia mais

AULA 06: PROGRAMAÇÃO EM MÁQUINAS PARALELAS

AULA 06: PROGRAMAÇÃO EM MÁQUINAS PARALELAS ORGANIZAÇÃO E ARQUITETURA DE COMPUTADORES II AULA 06: PROGRAMAÇÃO EM MÁQUINAS PARALELAS Prof. Max Santana Rolemberg Farias max.santana@univasf.edu.br Colegiado de Engenharia de Computação PROGRAMAÇÃO PARALELA

Leia mais

Carlos Eduardo Batista Centro de Informática - UFPB

Carlos Eduardo Batista Centro de Informática - UFPB Carlos Eduardo Batista Centro de Informática - UFPB bidu@ci.ufpb.br Motivação Arquitetura de computadores modernos Desafios da programação concorrente Definição de concorrência Correr junto Disputa por

Leia mais

Celso L. Mendes LAC /INPE

Celso L. Mendes LAC /INPE Arquiteturas para Processamento de Alto Desempenho (PAD) Aula 9 Celso L. Mendes LAC /INPE Email: celso.mendes@inpe.br Aula 9 (3/5): E. Aceleradores Estrutura Planejada i. Estruturas mais Populares ii.

Leia mais

Processadores para computação de alto desempenho

Processadores para computação de alto desempenho Processadores para computação de alto desempenho Aleardo Manacero Jr. DCCE/UNESP Grupo de Sistemas Paralelos e Distribuídos Introdução Nesta aula apresentaremos características de processadores e como

Leia mais

Paralelização do Detector de Bordas Canny para a Biblioteca ITK usando CUDA

Paralelização do Detector de Bordas Canny para a Biblioteca ITK usando CUDA Paralelização do Detector de Bordas Canny para a Biblioteca ITK usando CUDA Luis Henrique Alves Lourenço Grupo de Visão, Robótica e Imagens Universidade Federal do Paraná 7 de abril de 2011 Sumário 1 Introdução

Leia mais

Aplicações em CUDA. Medialab Instituto de Computação Universidade Federal Fluminense NVIDIA CUDA Research Center

Aplicações em CUDA. Medialab Instituto de Computação Universidade Federal Fluminense NVIDIA CUDA Research Center Aplicações em CUDA Medialab Instituto de Computação Universidade Federal Fluminense NVIDIA CUDA Research Center Roteiro l Introdução l Eventos l Aspectos históricos l Operações atômicas l Introdução sobre

Leia mais

Introdução a CUDA. Esteban Walter Gonzalez Clua. Medialab - Instituto de Computação Universidade Federal Fluminense NVIDIA CUDA Research Center START

Introdução a CUDA. Esteban Walter Gonzalez Clua. Medialab - Instituto de Computação Universidade Federal Fluminense NVIDIA CUDA Research Center START Introdução a CUDA START Esteban Walter Gonzalez Clua Medialab - Instituto de Computação Universidade Federal Fluminense NVIDIA CUDA Research Center 1536 cores Dynamic Parallelism Hyper - Q Pipeline

Leia mais

Organização Básica de Computadores. Organização Básica de Computadores. Organização Básica de Computadores. Organização Básica de Computadores

Organização Básica de Computadores. Organização Básica de Computadores. Organização Básica de Computadores. Organização Básica de Computadores Ciência da Computação Arq. e Org. de Computadores Processadores Prof. Sergio Ribeiro Composição básica de um computador eletrônico digital: Processador Memória Memória Principal Memória Secundária Dispositivos

Leia mais

Organização de Computadores Sistema de entrada e saída (I/O) e computação paralela. Professor: Francisco Ary

Organização de Computadores Sistema de entrada e saída (I/O) e computação paralela. Professor: Francisco Ary Organização de Computadores Sistema de entrada e saída (I/O) e computação paralela Professor: Francisco Ary Computação Paralela Capacidade de um sistema computacional ser executado de forma simultânea,

Leia mais

Computadores podem ser úteis em problemas que envolvem: Grande número de dados. Grande número de cálculos. Complexidade. Precisão.

Computadores podem ser úteis em problemas que envolvem: Grande número de dados. Grande número de cálculos. Complexidade. Precisão. O uso do computador Computadores podem ser úteis em problemas que envolvem: Grande número de dados. Grande número de cálculos. Complexidade. Precisão. Exemplos: Modelos meteorológicos. Cálculo estrutural.

Leia mais

de petróleo. Um novo domínio chamado computação de propósito geral em processadores gráficos (GPGPU) surgiu quando os pipelines de gráficos de

de petróleo. Um novo domínio chamado computação de propósito geral em processadores gráficos (GPGPU) surgiu quando os pipelines de gráficos de 12 1 1.1. Motivações Dentre os tipos de técnicas de Inteligência Artificial existentes, as técnicas de Programação Genética (PG) continuam mudando rapidamente conforme os pesquisadores e profissionais

Leia mais

Arquitetura e Organização de Processadores. Aula 1. Introdução Arquitetura e Organização

Arquitetura e Organização de Processadores. Aula 1. Introdução Arquitetura e Organização Universidade Federal do Rio Grande do Sul Instituto de Informática Programa de Pós-Graduação em Computação Arquitetura e Organização de Processadores Aula 1 Introdução Arquitetura e Organização 1. Arquitetura

Leia mais

GPU (Graphics Processing Unit) Bruno Padilha Gregory De Bonis Luciana Kayo

GPU (Graphics Processing Unit) Bruno Padilha Gregory De Bonis Luciana Kayo GPU (Graphics Processing Unit) Bruno Padilha - 5745282 Gregory De Bonis - 6431180 Luciana Kayo - 6430992 O que é? O que é? - Processador auxiliar responsável principalmente por operações de ponto flutuante

Leia mais

SSC510 Arquitetura de Computadores. 6ª aula

SSC510 Arquitetura de Computadores. 6ª aula SSC510 Arquitetura de Computadores 6ª aula PARALELISMO EM NÍVEL DE PROCESSOS PROFA. SARITA MAZZINI BRUSCHI Tipos de Paralelismo Instrução (granulosidade fina) Paralelismo entre as instruções Arquiteturas

Leia mais

Processadores para computação de alto desempenho

Processadores para computação de alto desempenho Processadores para computação de alto desempenho Aleardo Manacero Jr. DCCE/UNESP Grupo de Sistemas Paralelos e Distribuídos Introdução Nesta aula apresentaremos características de processadores e como

Leia mais

AULA 03: PROCESSAMENTO PARALELO: MULTIPROCESSADORES

AULA 03: PROCESSAMENTO PARALELO: MULTIPROCESSADORES ORGANIZAÇÃO E ARQUITETURA DE COMPUTADORES II AULA 03: PROCESSAMENTO PARALELO: MULTIPROCESSADORES Prof. Max Santana Rolemberg Farias max.santana@univasf.edu.br Colegiado de Engenharia de Computação MULTIPROCESSADORES

Leia mais

Programação de Alto Desempenho - 2. Prof: Carla Osthoff

Programação de Alto Desempenho - 2. Prof: Carla Osthoff Programação de Alto Desempenho - 2 Prof: Carla Osthoff E-mail: osthoff@lncc.br 3- Modelos de programação paralela Shared Memory/Threads Posix Win32 treads OpenMP Message Passing MPI Data Parallel OpenCL/Cuda

Leia mais

Sistemas Distribuídos

Sistemas Distribuídos Sistemas Distribuídos Classificação de Flynn Fonte: Professoras. Sarita UFRJ e Thais V. Batista - UFRN Arquiteturas Paralelas Computação Paralela Conceitos Permite a execução das tarefas em menor tempo,

Leia mais

INTRODUÇÃO À TECNOLOGIA DA INFORMAÇÃO ORGANIZAÇÃO COMPUTACIONAL

INTRODUÇÃO À TECNOLOGIA DA INFORMAÇÃO ORGANIZAÇÃO COMPUTACIONAL INTRODUÇÃO À TECNOLOGIA DA ORGANIZAÇÃO COMPUTACIONAL PROFESSOR CARLOS MUNIZ ORGANIZAÇÃO DE UM COMPUTADOR TÍPICO Memória: Armazena dados e programas Processador (CPU - Central Processing Unit): Executa

Leia mais

Modelo de Von Neumann

Modelo de Von Neumann 1 Modelo de Von Neumann Memória UC ALU Entrada Saída ACC 2 Arquitetura de Von Neumann 3 O Computador 4 Processador Microprocessadores São processadores contidos em um único encapsulamento (CI). Microcontroladores

Leia mais

Caracterização de Sistemas Distribuídos

Caracterização de Sistemas Distribuídos Caracterização de Sistemas Distribuídos Roteiro Conceitos de Hardware Conceitos de Software Classificação de Flynn Classificação baseada no acesso a memória 2 Conceitos de HW Múltiplas CPUs Diferentes

Leia mais

Arquitetura de Computadores Paralelos. Introdução Conceitos Básicos Ambientes de Programação Modelos de Programação Paralela

Arquitetura de Computadores Paralelos. Introdução Conceitos Básicos Ambientes de Programação Modelos de Programação Paralela Arquitetura de Computadores Paralelos Introdução Conceitos Básicos Ambientes de Programação Modelos de Programação Paralela Por que estudar Computação Paralela e Distribuída? Os computadores sequenciais

Leia mais

Parte I Multiprocessamento

Parte I Multiprocessamento Sistemas Operacionais I Estrutura dos SO Prof. Gregorio Perez gregorio@uninove.br 2004 Parte I Multiprocessamento Roteiro 1 Multiprocessadores em Sistemas Fortemente Acoplados 1.1 1.2 1.3 Processamento

Leia mais

2. Conceitos Básicos. Introdução à Ciência da Computação.

2. Conceitos Básicos. Introdução à Ciência da Computação. 2. Conceitos Básicos Introdução à Ciência da Computação http://www.inf.unioeste.br/~claudia/icc2017.html Sumário Computador Processamento de dados Hardware Software Sistemas Arquivos Modalidades de Computadores

Leia mais

Hierarquia de memória:

Hierarquia de memória: INE5645 Programação Paralela e Distribuída Aluno Modelo de Execução CUDA - A execução do programa controlado pela CPU pode lançar kernels, que são trechos de código executados em paralelo por múltiplas

Leia mais

30/5/2011. Sistemas computacionais para processamento paralelo e distribuído

30/5/2011. Sistemas computacionais para processamento paralelo e distribuído Arquitetura de Computadores Sistemas computacionais para processamento paralelo e distribuído Prof. Marcos Quinet Universidade Federal Fluminense UFF Pólo Universitário de Rio das Ostras - PURO Processamento

Leia mais

PLANEJAMENTO DAS DISCIPLINAS DE SISTEMAS DIGITAIS NA EC3. Workshop de Graduação do PCS Prof. Edson S. Gomi 31 de julho de 2018

PLANEJAMENTO DAS DISCIPLINAS DE SISTEMAS DIGITAIS NA EC3. Workshop de Graduação do PCS Prof. Edson S. Gomi 31 de julho de 2018 PLANEJAMENTO DAS DISCIPLINAS DE SISTEMAS DIGITAIS NA EC3 Workshop de Graduação do PCS Prof. Edson S. Gomi 31 de julho de 2018 Disciplina PréRequisito Semestral Quadrimestral PCS3115 Sistemas Digitais I

Leia mais

Nome: N.º Ano: Turma: Turno: Responde às seguintes questões 1. Quais as vantagens da utilização de transístores face às válvulas de vácuo?

Nome: N.º Ano: Turma: Turno: Responde às seguintes questões 1. Quais as vantagens da utilização de transístores face às válvulas de vácuo? ANO LETIVO 2018/2019 FICHA DE AVALIAÇÃO DE ARQUITETURA DE COMPUTADORES Módulo Nº: 4 Data: 14/03/20189 Tipo de Prova: Teórica Classificação: O Docente: (Rafael Henriques) Nome: N.º Ano: Turma: Turno: Leia

Leia mais

ORGANIZAÇÃO DE COMPUTADORES

ORGANIZAÇÃO DE COMPUTADORES Organização de Computadores ORGANIZAÇÃO DE COMPUTADORES Curso: Tecnologia em Gestão da Tecnologia da Informação Ano: 2011 Conhecida como Processador ou é o cerebro do computador Unifica todo sistema e

Leia mais

1 Introdução. I know because I must know. It's my purpose. It's the reason I'm here. (The Matrix) 1.1 Objetivos do trabalho

1 Introdução. I know because I must know. It's my purpose. It's the reason I'm here. (The Matrix) 1.1 Objetivos do trabalho 1 Introdução I know because I must know. It's my purpose. It's the reason I'm here. (The Matrix) 1.1 Objetivos do trabalho Os hardwares gráficos atualmente podem ser considerados como verdadeiros processadores

Leia mais

COMPARAÇÃO DE DESEMPENHO ENTRE IMPLEMENTAÇÕES DO ALGORITMO JOGO DA VIDA COM PTHREAD E OPEMMP 1

COMPARAÇÃO DE DESEMPENHO ENTRE IMPLEMENTAÇÕES DO ALGORITMO JOGO DA VIDA COM PTHREAD E OPEMMP 1 COMPARAÇÃO DE DESEMPENHO ENTRE IMPLEMENTAÇÕES DO ALGORITMO JOGO DA VIDA COM PTHREAD E OPEMMP 1 Márcia Da Silva 2, Igor Gamste Haugg 3, Eliézer Silveira Prigol 4, Édson L. Padoin 5, Rogério S. M. Martins

Leia mais

Universidade Estadual de Mato Grosso do Sul UEMS Curso de Ciência da Computação Disciplina de Algoritmos Paralelos e Distribuídos

Universidade Estadual de Mato Grosso do Sul UEMS Curso de Ciência da Computação Disciplina de Algoritmos Paralelos e Distribuídos Universidade Estadual de Mato Grosso do Sul UEMS Curso de Ciência da Computação Disciplina de Algoritmos Paralelos e Distribuídos Pensando em Paralelo Pensar em paralelo é uma tarefa que exige disciplina

Leia mais

Ambientes e Ferramentas de Programação para GPU. Denise Stringhini (Mackenzie) Rogério Gonçalves (UFTPR/IME- USP) Alfredo Goldman (IME- USP)

Ambientes e Ferramentas de Programação para GPU. Denise Stringhini (Mackenzie) Rogério Gonçalves (UFTPR/IME- USP) Alfredo Goldman (IME- USP) Ambientes e Ferramentas de Programação para GPU Denise Stringhini (Mackenzie) Rogério Gonçalves (UFTPR/IME- USP) Alfredo Goldman (IME- USP) Conteúdo Conceitos de paralelismo Arquitetura de GPU CUDA OpenCL

Leia mais

Implementação de um escalonador de processos em GPU

Implementação de um escalonador de processos em GPU Implementação de um escalonador de processos em GPU Guilherme Martins guilhermemartins@usp.br 6 de abril de 2017 Guilherme Martins (guilhermemartins@usp.br) Implementação de um escalonador de processos

Leia mais

ORGANIZAÇÃO E ARQUITETURA DE COMPUTADORES II AULA 02: PROCESSAMENTO PARALELO: PROCESSADORES VETORIAIS

ORGANIZAÇÃO E ARQUITETURA DE COMPUTADORES II AULA 02: PROCESSAMENTO PARALELO: PROCESSADORES VETORIAIS ORGANIZAÇÃO E ARQUITETURA DE COMPUTADORES II AULA 02: PROCESSAMENTO PARALELO: PROCESSADORES VETORIAIS Prof. Max Santana Rolemberg Farias max.santana@univasf.edu.br Colegiado de Engenharia de Computação

Leia mais

2º Estudo Dirigido CAP 3

2º Estudo Dirigido CAP 3 2º Estudo Dirigido CAP 3 1. Cite três exemplos de aspecto na definição e implementação de uma arquitetura que são influenciados pelas características do conjunto de instruções? R.: Operações lógicas e

Leia mais

Universidade Federal do Rio de Janeiro Informática DCC/IM. Arquitetura de Computadores II. Arquiteturas MIMD. Arquiteturas MIMD

Universidade Federal do Rio de Janeiro Informática DCC/IM. Arquitetura de Computadores II. Arquiteturas MIMD. Arquiteturas MIMD Universidade Federal do Rio de Janeiro Informática DCC/IM Arquitetura de Computadores II Arquiteturas MIMD Arquiteturas MIMD As arquiteturas MIMD dividem-se em dois grandes modelos: Arquiteturas MIMD de

Leia mais

DESENVOLVIMENTO DE UM ALGORITMO PARALELO PARA APLICAÇÃO EM CLUSTER DE COMPUTADORES

DESENVOLVIMENTO DE UM ALGORITMO PARALELO PARA APLICAÇÃO EM CLUSTER DE COMPUTADORES DESENVOLVIMENTO DE UM ALGORITMO PARALELO PARA APLICAÇÃO EM CLUSTER DE COMPUTADORES João Ricardo Kohler Abramoski (PAIC/FUNDAÇÃO ARAUCÁRIA), Sandra Mara Guse Scós Venske (Orientadora), e-mail: ssvenske@unicentro.br

Leia mais

Introdução à Computação: Sistemas de Computação

Introdução à Computação: Sistemas de Computação Introdução à Computação: Sistemas de Computação Beatriz F. M. Souza (bfmartins@inf.ufes.br) http://inf.ufes.br/~bfmartins/ Computer Science Department Federal University of Espírito Santo (Ufes), Vitória,

Leia mais

INTRODUÇÃO À ARQUITETURA E ORGANIZAÇÃO DE COMPUTADORES. Função e Estrutura. Introdução Organização e Arquitetura. Organização e Arquitetura

INTRODUÇÃO À ARQUITETURA E ORGANIZAÇÃO DE COMPUTADORES. Função e Estrutura. Introdução Organização e Arquitetura. Organização e Arquitetura Introdução Organização e Arquitetura INTRODUÇÃO À ARQUITETURA E ORGANIZAÇÃO DE COMPUTADORES Eduardo Max Amaro Amaral Arquitetura são os atributos visíveis ao programador. Conjunto de instruções, número

Leia mais

Infraestrutura de Hardware. Funcionamento de um Computador

Infraestrutura de Hardware. Funcionamento de um Computador Infraestrutura de Hardware Funcionamento de um Computador Computador: Hardware + Software Perguntas que Devem ser Respondidas ao Final do Curso Como um programa escrito em uma linguagem de alto nível é

Leia mais

SSC0611 Arquitetura de Computadores

SSC0611 Arquitetura de Computadores SSC0611 Arquitetura de Computadores 20ª Aula Arquiteturas Paralelas Arquitetura MIMD com Memória Compartilhada Profa. Sarita Mazzini Bruschi sarita@icmc.usp.br Arquiteturas MIMD As arquiteturas MIMD dividem-se

Leia mais

Infraestrutura de Hardware. Processamento Paralelo Multicores, Multi-Threading e GPUs

Infraestrutura de Hardware. Processamento Paralelo Multicores, Multi-Threading e GPUs Infraestrutura de Hardware Processamento Paralelo Multicores, Multi-Threading e GPUs Perguntas que Devem ser Respondidas ao Final do Curso Como um programa escrito em uma linguagem de alto nível é entendido

Leia mais

28 de fevereiro de 2016

28 de fevereiro de 2016 Ítalo Mendes da Silva Ribeiro UESPI 28 de fevereiro de 2016 1 / 72 Súmario Breve 2 / 72 Súmario Breve 3 / 72 Por que estudar Arquitetura e Organização de Computadores? Conhecimento do funcionamento interno

Leia mais

Uma introdução para computação paralela de modelos massivos. Adriano Brito Pereira inf.puc-rio.br

Uma introdução para computação paralela de modelos massivos. Adriano Brito Pereira inf.puc-rio.br Uma introdução para computação paralela de modelos massivos Adriano Brito Pereira 1021752 apereira @ inf.puc-rio.br Departamento de Informática Novembro / 2010 1 Resultados obtivos com Manta Framework

Leia mais

Organização e Arquitetura de Computadores I

Organização e Arquitetura de Computadores I Organização e Arquitetura de Computadores I BARRAMENTO Slide 1 Sumário Introdução Componentes de Computador Funções dos Computadores Estruturas de Interconexão Interconexão de Barramentos Slide 2 Introdução

Leia mais

Arranjo de Processadores

Arranjo de Processadores Um arranjo síncrono de processadores paralelos é chamado arranjo de processadores, consistindo de múltiplos elementos processadores (EPs) sob a supervisão de uma unidade de controle (UC) Arranjo de processadores

Leia mais

Processamento Paralelo

Processamento Paralelo Processamento Paralelo por Helcio Wagner da Silva Introdução Tradicionalmente, o computador tem sido visto como uma máquina seqüencial Esta visão nunca foi completamente verdadeira No nível das µo, vários

Leia mais

Paradigmas de Processamento Paralelo na Resolução do Fractal de Mandelbrot

Paradigmas de Processamento Paralelo na Resolução do Fractal de Mandelbrot Paradigmas de Processamento Paralelo na Resolução do Fractal de Mandelbrot Bruno Pereira dos Santos Dany Sanchez Dominguez Universidade Estadual de Santa Cruz Cronograma Introdução Serial vs Processamento

Leia mais

ARQUITETURA DE COMPUTADORES. Organização de Sistemas Computacionais. Prof.: Agostinho S. Riofrio

ARQUITETURA DE COMPUTADORES. Organização de Sistemas Computacionais. Prof.: Agostinho S. Riofrio ARQUITETURA DE COMPUTADORES Organização de Sistemas Computacionais Prof.: Agostinho S. Riofrio Agenda 1. Unidade Central de Processamento 2. Organização da CPU 3. Interpretador 4. RISC x CISC 5. Principios

Leia mais

Processamento Paralelo Utilizando GPU

Processamento Paralelo Utilizando GPU Processamento Paralelo Utilizando GPU Universidade Estadual de Santa Cruz Bruno Pereira dos Santos Dany Sanchez Dominguez Esbel Evalero Orellana Cronograma Breve introdução sobre processamento paralelo

Leia mais

Introdução à Informática Engenharia Agrícola

Introdução à Informática Engenharia Agrícola Conceitos Básicos Introdução à Informática Engenharia Agrícola Sumário Computador Processamento de dados Hardware Software Sistemas Arquivos Modalidades de Computadores Arquitetura Básica. Uma referência

Leia mais

FPGA & VHDL. Tutorial

FPGA & VHDL. Tutorial FPGA & VHDL Tutorial 2009-2 FPGA FieldProgrammableGateArray Dispositivo lógico contendo uma matriz de: Células lógicas genéricas Configuráveis ( programadas ) para desempenhar uma função simples Chaves

Leia mais

Montagem e manutenção de computadores

Montagem e manutenção de computadores Montagem e manutenção de computadores Processadores Prof. Patrícia Lucas Processadores 1 O processador é o responsável por executar instruções de máquina. A CPU (Unidade central de processamento): é o

Leia mais

Arquitetura de Computadores

Arquitetura de Computadores Arquitetura de Computadores 2018.1 Computador O computador é uma máquina que realiza processamento de dados automaticamente. Ela é formada por um hardware e um software. O Engenho Analítico é o primeiro

Leia mais

Arquitetura de Computadores Aula 11 - Multiprocessamento

Arquitetura de Computadores Aula 11 - Multiprocessamento Arquitetura de Computadores Aula 11 - Multiprocessamento Prof. Dr. Eng. Fred Sauer http://www.fredsauer.com.br fsauer@gmail.com 1/28 PROCESSAMENTO PARALELO OBJETIVO: aumentar a capacidade de processamento.

Leia mais

Organização de Computadores II. Arquiteturas MIMD

Organização de Computadores II. Arquiteturas MIMD Organização de Computadores II Arquiteturas MIMD Arquiteturas UMA Arquiteturas com memória única global. Tempo de acesso uniforme para todos os nós de processamento. Nós de processamento e memória interconectados

Leia mais

SSC510 Arquitetura de Computadores 1ª AULA

SSC510 Arquitetura de Computadores 1ª AULA SSC510 Arquitetura de Computadores 1ª AULA REVISÃO DE ORGANIZAÇÃO DE COMPUTADORES Arquitetura X Organização Arquitetura - Atributos de um Sistema Computacional como visto pelo programador, isto é a estrutura

Leia mais

Introdução OpenMP. Nielsen Castelo Damasceno

Introdução OpenMP. Nielsen Castelo Damasceno Introdução OpenMP Nielsen Castelo Damasceno Computação de auto desempenho Processamento Paralelo Memória Distribuída e Compartilhada Modelo de programação OpenMP Métricas de Desempenho Computação de auto

Leia mais

PROJETO LÓGICO DE COMPUTADORES Prof. Ricardo Rodrigues Barcelar

PROJETO LÓGICO DE COMPUTADORES Prof. Ricardo Rodrigues Barcelar - Aula 1 - O NÍVEL DA LÓGICA DIGITAL 1. INTRODUÇÃO Na parte inferior da hierarquia da figura abaixo encontramos o nível da lógica digital, o verdadeiro hardware do computador. Este nível situa-se na fronteira

Leia mais

CUDA: Compute Unified Device Architecture. Marco Antonio Simões Teixeira

CUDA: Compute Unified Device Architecture. Marco Antonio Simões Teixeira CUDA: Compute Unified Device Architecture Marco Antonio Simões Teixeira Sumário Introdução; CUDA: História; CUDA: programando; CUDA e deep learning; Links úteis; Considerações finais. 2 INTRODUÇÃO 3 O

Leia mais

periféricos: interfaces humano-computador (HCI) arquivo de informação comunicações

periféricos: interfaces humano-computador (HCI) arquivo de informação comunicações Introdução aos Sistemas de Computação (6) Análise de componentes num computador Estrutura do tema ISC 1. Representação de informação num computador 2. Organização e estrutura interna dum computador 3.

Leia mais

O que é Arquitetura de Computadores?

O que é Arquitetura de Computadores? O que é Arquitetura de Computadores? Coordenação de um conjunto de níveis de abstração de um computador sobre um grande conjunto de forças de mudança Arquitetura de Computadores = Arquitetura de Conjuntos

Leia mais

SSC0611 Arquitetura de Computadores

SSC0611 Arquitetura de Computadores SSC0611 Arquitetura de Computadores 5ª e 6ª Aulas Revisão de Hierarquia de Memória Profa. Sarita Mazzini Bruschi sarita@icmc.usp.br 1 Memória Memória Todo componente capaz de armazenar bits de informação

Leia mais

Arquitetura de Computadores

Arquitetura de Computadores Arquitetura de Computadores 2018.1 Relembrando... Paralelismo Relembrando... Paralelismo Paralelismo em Instrução Relembrando... Paralelismo Paralelismo em Instrução Paralelismo em Aritmética Relembrando...

Leia mais

Arquitetura e organização de computadores Uma visão geral

Arquitetura e organização de computadores Uma visão geral Arquitetura e organização de computadores Uma visão geral MAC 344 - Arquitetura de Computadores Prof. Siang Wun Song Baseado em W. Stallings - Computer Organization and Architecture Objetivo do disciplina

Leia mais

PIPELINE. Introdução ao Pipeline. PIPELINE Ciclo de Instruções. PIPELINE Ciclo de Instruções. PIPELINE - Exemplo. PIPELINE Considerações

PIPELINE. Introdução ao Pipeline. PIPELINE Ciclo de Instruções. PIPELINE Ciclo de Instruções. PIPELINE - Exemplo. PIPELINE Considerações Arquitetura de Computadores Introdução ao Pipeline PIPELINE Linha de Montagem A produção é dividida em várias etapas Produtos em etapas distintas podem ser desenvolvidos separadamente Pode ser Aplicado

Leia mais

Unidade 12: Introdução ao Paralelismo:

Unidade 12: Introdução ao Paralelismo: Arquitetura e Organização de Computadores 1 Unidade 12: Introdução ao Paralelismo: Processadores Superescalares Prof. Daniel Caetano Objetivo: Apresentar os conceitos fundamentais da arquitetura superescalar

Leia mais