Como mencionado anteriormente, faremos o uso de transformações unimodulares. Esta classe de transformações provoca uma alteração na timing function T, que pode ser vista como uma forma diferente de proceder-se à varredura do espaço de iterações.
Com o intuito de facilitar a exposição de conceitos sobre a transformação, particularizaremos a análise para o caso das dependências que existem em um programa de multiplicação de matrizes, o qual será posteriormente objeto do exemplo de implementação do algoritmo completo proposto.
Dado a seguinte estrutura de loops:
for (i1=0; i1<N1; i1++)
for (i2=0; i2<N2; i2++)
for (i3=0;i3<N3; i3++)
A[iA(1)][iA(2)]=A[iA(1)][iA(2)]+B[iB(1)][iB(2)]*C[iC(1)][iC(2)]
onde h: Z3 Z2, com:
hA(i1,i2,i3) = (iA(1), iA(2)) hB(i1,i2,i3) = (iB(1), iB(2)) hC(i1,i2,i3) = (iC(1), iC(2))Então, a transformação é definida por:
I' = I U
onde U é a matriz unimodular, I é o vetor com ordenamento original dos índices dos loops e I' é o vetor resultante da transformação aplicada.
Definamos a matriz de mapeamento M como:
h = I M
ou seja, a matriz M é aquela que multiplicada pelo vetor de índices fornece o vetor de mapeamento h.
O processo de transformação do ordenamento dos loops deve manter, contudo, o mapeamento em cada dependência, uma vez que é necessário a manutenção da semântica do programa. Assim, procedido à transformação, o mapeamento h calcula-se por:
h = I U U-1 M = I' M'
onde U-1 M = M' é a nova matriz de mapeamento.
Dispondo-se dessas definições podemos montar um algoritmo de otimização da localidade de dados, tendo como critério, a obtenção do menor custo da janela de referência:
Algoritmo: Otimização integrada de paralelismo e localidadade de dados
Entrada:
número de loops K
vetores de dependências D
mapeamentos PI
{
/* determina o custo de cada uma das permutações permitidas dos loops */
for ( cada uma das permutações )
if (transformação válida )
determina custo em termos de acessos à memória
else
marca transformação com inválida
ordena as permutações válidas em ordem de custo
for ( cada uma das permutações )
{
obtém novos vetores de dependências
if ( é possível paralelizar loop externo )
break; /* achou transformação adequada */
}
if ( não é possível paralelizar loop externo )
obtém transformação que paraleliza loop interno
obtém novos mapeamentos
re-otimiza loops internos em termos de memória
retorna versão do programa com o menor custo
}
Deste modo, teremos ao final, um código com a melhor localidade de dados
passível de paralelização. Um procedimento mais
genérico onde se levará em conta aspectos relacionados com a
paralelização, tais como overheads de
sincronização e de criação dos processos paralelos,
e a incorporação de outras transformações
está em desenvolvimento.