-
Notifications
You must be signed in to change notification settings - Fork 77
Simd I
Objetivos de aprendizado:
- Conhecer principais pontos da supercomputação
- Programar para arquiteturas vetoriais
- Habilitar auto vetorização e conhecer suas limitações
Nesta primeira tarefa iremos compilar alguns códigos em C++ para Assembly usando
as opções -ffast-math -ftree-vectorize -mavx
do gcc
. Estas opções habilitam a autovetorização
de código. Ou sejam elas analisam o código e procuram substituir loops e acessos sequenciais
a vetores por instruções SIMD que façam o mesmo trabalho.
A flag -mavx
indica que o código de máquina gerado pode utilizar instruções
SIMD da arquitetura AVX (e de sua predecessora, SSE), que usa registradores de 128 bits nomeados %xmm0
até %xmm7
e de 256 bits nomeados %ymm0
até %ymm15
. Ou seja, posso armazenar, em um registrador
%xmm0
- ____ chars;
- ____ shorts;
- ____ ints;
- ____ longs;
- ____ floats;
- ____ doubles;
Para registradores %ymm
é só dobrar os valores acima.
Toda instrução SIMD opera sobre todos os elementos guardados ao mesmo tempo. Ou seja, ao executar uma instrução SIMD de soma de variáveis int
no registrador %xmm0
estarei somando ____ variáveis em uma só instrução.
Vamos agora analisar o código Assembly de uma função simples que soma todos elementos de um vetor.
\pagebreak
Primeiro, compile este código para Assembly sem SIMD.
$ g++ -S -c -O2 tarefa1.cpp -o /dev/stdout | c++filt
Agora, compile o mesmo programa habilitando a autovetorização.
$ g++ -S -c -O2 -ffast-math -ftree-vectorize -mavx tarefa1.cpp -o /dev/stdout | c++filt
Discussão 1: Você consegue identificar onde os códigos diferem?
Nesta tarefa iremos trabalhar com as opções de autovetorização do gcc
para entender como escrever código que possa ser otimizado automaticamente.
Escreva uma função main
que gera um vetor de tamanho 10.000.000
contendo números aleatórios uniformemente distribuídos entre -10
e 10
. Use as funções do cabeçalho <random>
.
Escreva uma função double soma_positivos1(double *a, int n)
que soma todos os números positivos do vetor a
. Adicione uma chamada a esta função no seu main
e use as funções do cabeçalho <chrono>
para medir o tempo de execução da sua função. Neste exercício você deverá usar um if
para checar se os números são positivos.
Compile com e sem as otimizações SIMD e verifique se há diferença no tempo de execução.
O auto vetorizador suporta uma série de padrões de codificação relativamente abrangente (lista completa). Porém, códigos que são vetorizados de maneira idêntica possuem desempenho bastante diferente quanto a vetorização não está habilitada. Faça uma função double soma_positivos2(double *a, int n)
que faz o mesmo que a função anterior, mas usando agora o operador ternário (cond)?expr_true:expr_false
ao invés de um if
. (Se você fez com o operador ternário acima faça com if
). Houve diferença de desempenho na versão SIMD? E na versão sem SIMD?
Qual versão da função anterior você usaria se seu código fosse executado em processadores de baixo custo (Intel Celeron) ou muito antigos (mais de 5 anos)? E se o plano for executar em processadores novos?
\pagebreak
Nas últimas duas tarefas vimos como usar as opções do compilador para gerar instruções SIMD, tornando nossos programas mais eficientes. Agora vamos aplicá-las ao nosso exemplo dos experimentos. Você deverá
- criar 5 executáveis diferentes a partir do seu main.cpp
-
O0
atéO3
- sem otimizações -
O3
+ flags SIMD
-
- criar um jupyter notebook que rode os 5 executáveis e leia os tempos de execução
- plotar um gráfico para cada experimento contendo os tempos das 5 versões testadas para cada tipo de função rodada.
Com os resultados do seu gráfico em mãos você deverá responder as seguintes perguntas:
- A partir de qual tamanho de array o código vetorizado mostra ganhos de desempenho expressivos?
- Qual é o ganho de desempenho esperado? Leve em conta a arquitetura usada e o tipo de dados usado.
- Os ganhos de desempenho são consistentes com o esperado?
- Quais tipos de operações resultam em maior ganho de desempenho?