Documente Academic
Documente Profesional
Documente Cultură
3 Otimizações do compilador
a[i]=b[i]*c[i];
Esse loop é executado um número fixo de vezes, a saber, N. Uma implementação direta
do loop criaria e inicializaria a variável de loop i, atualizaria seu valor em cada iteração e teste-o
para ver se deve sair do loop. No entanto, porque o loop é executado um número fixo de vezes,
podemos gerar mais código direto.
Se deixarmos N = 4, então podemos substituir este código em linha reta pelo loop:
a[0] = b[0]*c[0];
a[1] = b[1]*c[1];
a[2] = b[2]*c[2];
a[3] = b[3]*c[3];
Este código desenrolado não possui nenhum código de sobrecarga de loop, isto é,
nenhuma variável de iteração e nenhum teste. Mas o loop desenrolado tem os mesmos
problemas que o procedimento inline - ele pode interferir o cache e expande a quantidade de
código necessária. Não temos, é claro, que desenrolar totalmente os loops. Em vez de
desenrolar o loop acima, quatro vezes, podemos desenrolá-lo duas vezes. Desenrolar produz
este código:
a[i*2] = b[i*2]*c[i*2];
Neste caso, porque todas as operações nas duas linhas do corpo do loop são
independentes, estágios posteriores do compilador podem gerar código que permita que sejam
executados eficientemente no pipeline da CPU.
A fusão de loop combina dois ou mais loops em um único loop. Para que essa
transformação seja legal, duas condições devem ser satisfeitas. Primeiro, os loops devem iterar
os mesmos valores. Segundo, os corpos dos laços não devem ter dependências que seriam
violados se forem executados juntos - por exemplo, se a segunda iteração do loop depende dos
resultados da iteração i + 1 do primeiro loop, os dois loops não podem ser combinado. A
distribuição de loop é o oposto da fusão de loop, ou seja, decompor um único loop em vários
loops.
Loop tiling divide um loop em um conjunto de loops aninhados, com cada loop interno
executando as operações em um subconjunto dos dados. Um exemplo é mostrado na Figura
5.17. Aqui, cada loop é dividido em blocos de tamanho dois. Cada loop é dividido em dois loops
— por exemplo, o loop interno ii itera dentro do bloco e o loop externo i itera através dos
azulejos. O resultado é que o padrão de acessos em toda a matriz é drasticamente diferente -
em vez de percorrer uma linha em sua totalidade, o código caminha através de linhas e colunas
seguindo a estrutura do tile. Loop tiling altera o pedido em que elementos da matriz são
acessados, permitindo-nos controlar melhor o comportamento do cache durante a execução do
loop.
Código morto é um código que nunca pode ser executado. Código morto pode ser
gerado por programadores, inadvertidamente ou propositalmente. Código morto também pode
ser gerado por compiladores. O código morto pode ser identificado pela análise de alcance -
encontrar as outras declarações ou instruções a partir das quais pode ser alcançado. Se um
determinado pedaço de código não pode ser alcançado, ou pode ser alcançado apenas por um
pedaço de código que é inacessível.