Você está na página 1de 2

5.5.

3 Otimizações do compilador

Técnicas básicas de compilação podem gerar código ineficiente. Compiladores usam


uma ampla gama de algoritmos para otimizar o código que eles geram. Loops são estruturas de
programa importantes, embora sejam descritas de forma compacta no código-fonte, eles
geralmente usam uma grande fração do tempo de computação. Muitas técnicas foram
projetadas para otimizar loops. Uma transformação simples, mas útil, é conhecida como loop
de desenrolamento, ilustrado no próximo exemplo. O desenrolamento de loop é importante
porque ajuda a expor o paralelismo que pode ser usado por fases posteriores do compilador.

Exemplo 5.5 Desenrolando Loop

Aqui está um loop C simples:

for (i = 0; i < N; i++) {

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:

for (i = 0; i < 2; i++) {

a[i*2] = b[i*2]*c[i*2];

a[i*2 + 1] = b[i*2 + 1]*c[i*2 + 1];

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.

Também podemos modificar as matrizes sendo indexadas em loops. Preenchimento de


matriz adiciona elementos de dados fictícios para um loop, a fim de alterar o layout da matriz
no cache. Embora esses locais de array não sejam usados, eles mudam a maneira como
elementos de matriz úteis caem em linhas de cache. Preenchimento judicioso pode, em alguns
casos reduz significativamente o número de conflitos de cache durante a execução do loop.

Eliminação de código morto

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.

Você também pode gostar