Técnicas para desenvolvimento e aceleração de códigos científicos
|
Nesta atividade, exercitaremos a aplicação de otimizações com o compilador.
Compiladores são ferramentas que auxiliam na tradução de código escrito em linguagens de programação para código em linguagem de máquina. Além de traduzir o código, os compiladores aplicam uma série de otimizações para melhorar o desempenho do mesmo. O conjunto de otimizações aplicados pelo compilador pode variar de acordo com o compilador, a versão do compilador ou mesmo com as opções de compilação. De fato, o usuário pode, em muitos casos, escolher o conjunto de otimizações que deve ser aplicado no código durante a tradução.
Nesta atividade, utilizaremos o compilador para aplicar diferentes conjuntos de otimizações no código e verificar o desempenho do código resultante.
Cada atividade possui um arquivo com código fonte para executar o algoritmo, medir o tempo de execução e reportar o tempo e o desempenho do acesso à memória, em Gigabytes por segundo (GB/s). O algoritmo é executado múltiplas vezes e os tempos médio, menor e maior são reportados.
O conjunto de otimizações e a ordem em que as otimizações são aplicadas podem afetar significativamente o desempenho do código resultante. Para que o usuário do compilador não tenha que escolher e habilitar manualmente as otimizações mais promissoras durante a compilação, os desenvolvedores de compiladores geralmente disponibilizam conjuntos pré-selecionados de otimizações. Estes conjuntos são geralmente habilitados com as flags: -O1, -O2, -Os e -O3, onde o -O3 habilita o conjunto mais agressivo (maior número de otimizações) e -O1 habilita o menor número de otimizações. A flag -O0 não habilita otimizações e gera um código sub-ótimo, equivalente à compilação sem flags de otimização.
Nesta atividade, vamos comparar o desempenho de uma aplicação quando compilada com diferentes opções de otimização.
gcc -O3 kernel10-array_sum.c -o kernel10-array_sum.xPara aplicar outros conjuntos basta substituir -O3 pela flag desejada.
Ao inspecionar o código do programa, você pode observar que:
Responda às seguintes perguntas:
Execute o experimento novamente, mas desta vez, modifique o código para
que a rotina clean_caches() não seja invocada entre as
iterações do kernel. O objetivo é manter os dados na
cache entre as iterações. Para que isso seja possível, os três vetores (ma,
mb e mc) devem caber na cache. Para tanto, você deve ajustar
o tamanho da definição ARRAY_SZ de forma que seus vetores caibam na cache.
Sugestão: Se seu computador tiver uma memória cache de 4MB (4*1024*1024 bytes), você pode
dimensionar o vetor da seguinte forma:
ARRAY_SZ=((4*1024*1024) / (3*sizeof(DATATYPE)))sizeof(DATATYPE) é o tamanho do tipo de dados utilizado no programa (4 bytes para float e 8 para double).
Responda às seguintes perguntas:
Inspecione o código em linguagem de montagem gerado com as flags -O2 e -O3.
Para gerar o código em linguagem de montagem com as flags -O3, execute:
gcc -O3 kernel10-array_sum.c -S -o kernel10-array_sum.sProcure pela rotina array_sum_naive e identifique o laço principal da rotina.