LU 2 : Análise de Desempenho de Algoritmos LU


Útima modificação : 14 de julho de 1995. This Page in English (comming soon)

SUMÁRIO

1 INTRODUÇÃO

2 SOLUÇÃO DE SISTEMAS LINEARES [2]

2.1 MÉTODO DE GAUSS

3 ALGORITMOS DE DECOMPOSIÇÃO LU [3]
4 RESULTADOS

4.1 DESEMPENHO DOS ALGORITMOS

Tabelas de Comparação de Desempenho

SUN SPARC 10
SGI POWER SERIES 4D/480
SGI INDIGO
SGI INDIGO II
4.2 Gráficos de comparação de desempenho
Gráfico 1 - Desempenho dos Algoritmos
Gráfico 2 - Comparação de Desempenho em Relação a versão KJI
5 CONCLUSÃO
6 BIBLIOGRAFIA
7 CÓDIGOS FONTE


1 INTRODUÇÃO


A solução de sistemas lineares por decomposição LU tem grande importância nos processos computacionais técnicos e científicos, principalmente na área de computação gráfica.

Para compreender a decomposição LU, é preciso antes conhecer os algoritmos de resolução de sistemas lineares pelo método da eliminação de Gauss, os quais estão intimamente ligados à decomposição LU.

Este trabalho foi baseado no artigo de J. M. Ortega [3] o qual faz a análise das várias formas da eliminação de Gauss baseada na forma genérica de três laços dada na figura 1.

     
for (k=0; k<n; k++)
    for (i=0; i<n; i ++)
        {
          L[i][k] = A[i][k] / A[k][k];
          for (j=0; j<n; j ++)
                A[i][j] -= L[i][k] * A[k][j];
        }
      
     

figura 1 - A forma genérica de laços aninhados da eliminação de Gauss

Para as seis permutações dos índices i, j e k correspondem as seis diferentes organizações da eliminação de Gauss as quais Ortega chamou de "formas ijk".


2 SOLUÇÃO DE SISTEMAS LINEARES [2]


2.1 MÉTODO DE GAUSS


O método de eliminação de Gauss consiste no seguinte:

Seja o sistema linear A.x = b onde A é uma matriz mxm, triangular superior com elementos da diagonal principal diferentes de zero. O sistema fica, então, da seguinte maneira:

     
a[1][1].x[1]+a[1][2].x[2]+a[1][3].x[3]+...+a[1][n].x[n] = b[1]
a[2][2].x[2]+a[2][3].x[3]+...+a[2][n].x[n] = b[2]
a[3][3].x[3]+...+a[3][n].x[n] = b[3]
		      .
		      .
		      .
a[n][n].x[n] = b[n]
     
    

figura 1b - Representação do sistema linear A.x = b

Da última equação, temos


x[n] = b[n] / a[n][n].

x[n-1] pode, então, ser obtido da penúltima equação:


x[n-1] = (b[n-1]-a[n-1][n].x[n]) / a[n-1][n-1]
e assim sucessivamente até x[1].

A partir disso, é possível descrever um algoritmo para a solução deste tipo de sistema linear.

Algoritmo

Dado um sistema triangular superior nxn com elementos da diagonal principal da matriz A não nulos. Então teremos:

e para k = n-1, n-2, ..., 2, 1, temos:

Com isso, o método da eliminação de Gauss consiste em transformar um sistema de equações lineares cuja matriz de coeficientes não é triangular superior num sistema equivalente - se existir - cuja matriz de coeficientes seja triangular superior. Para obter a matriz triangular superior, basta aplicar o seguinte algoritmo:

onde

Desta forma, podemos obter as matrizes L e U pelo método da eliminação de Gauss, onde:

sendo A' a matriz dos coeficientes resultante da aplicação do método da eliminação de Gauss.

Desta forma, temos a forma canônica do algoritmo do método da eliminação de Gauss:

     
for (k = 1; k <= n - 1; k++)
    {
      for (i = k + 1; i <= n; i ++)
          {
            L[i][k] /= A[k][k]; /* calcula pivo Lik */
            for (j = k + 1; j <= n; j ++)
                  A[i][j] -= L[i][k] * A[k][i];
          }
    }
     
    


3 ALGORITMOS DE DECOMPOSIÇÃO LU [3]


3.1 VERSÃO IJK

Descrição

A versão IJK calcula os elementos da matriz L por linha, sendo que para cada elemento L[i][j-1], é feito o cálculo do elemento A[i][j], subtraindo deste elemento os elementos da primeira linha até o elemento da linha j-2 da matriz A, multiplicados pelos elementos da linha i, da primeira coluna até o elemento da coluna j-2 da matriz L (A[i][j] = A[i][j] - L[i][k] * A[k][j]).

Algoritmo

  for (i = 1; i < n; i++)
      {
        for (j = 1; j <= i; j ++)
        {
          L[i][j-1] = A[i][j-1] / A[j-1][j-1];
          for (k = 0; k <= j - 1; k ++)
              {
                A[i][j] -= L[i][k] * A[k][j];
              }
         }
         for (j = i + 1; j < n; j ++)
             {
               for (k = 0; k <= i - 1; k ++)
                   {
                     A[i][j] -= L[i][k] *  A[k][j];
                   }
             }
     }
     

Acessos à memória

O acesso da matriz A é feito por colunas e o da matriz L por linhas (região cinza-escuro). Como na linguagem C as matrizes são armazenadas na memória por linhas, o acesso por colunas à matriz A pode ser considerada ruim já que não permite o aproveitamento do cache.

3.2 VERSÃO IJK

Descrição

Esta é a versão mais simples da decomposição LU. Nesta versão, para cada elemento L[i][k] calculado calcula-se os elementos da linha i e colunas de k+1 até n-1 da matriz A. Para cada elemento A[i][j] é subtraído o elemento da linha k, coluna j de A multiplicado pelo elemento da linha i, coluna k de L (A[i][j] = A[i][j] - L[i][k] * A[k][j]).

Algoritmo

for (i = 1; i < n; i ++)
    {
      for (k = 0; k <= i - 1; k ++)
          {
            L[i][k] = A[i][k] / A[k][k];
            for (j = k + 1; j < n; j ++)
                {
                  A[i][j] -= L[i][k] * A[k][j];
                }
          }
    }
    
     

Acessos à memória

Neste caso, tanto a matriz A quanto a matriz L são acessadas por linha, sendo então um dos melhores algoritmos.

3.3 VERSÃO JIK

Descrição

Nesta versão, para cada coluna j-1, são calculados os valores de L a partir da linha j até a linha n-1. Em seguida é feito o cálculo com os elementos da matriz A a partir da linha 1 até a linha j, calculando para cada linha i o elemento A[i][j], subtraindo deste elemento os elementos da coluna j, linhas de 0 até i-1 da matriz A multiplicados, respectivamente, pelos elementos da linha i, colunas de 0 até i-1 da matriz L (A[i][j] = A[i][j] - L[i][k] * A[k][j]). Por último, para cada elemento da matriz A a partir da linha j+1 até a linha n-1, são calculados os elementos A[i][j], subtraindo destes elementos os elementos da coluna j, linhas de 0 até j-1 da matriz A multiplicados pelos elementos da linha i, colunas de 0 até j-1 da matriz L (A[i][j] = A[i][j] - L[i][k] * A[k][j]).

Algoritmo

for (j = 1; j < n; j ++)
    {
      for (s = j; s < n; s ++)
          {
            L[s][j-1] = A[s][j-1] / A[j-1][j-1];
          }
      for (i = 1; i <= j; i ++)
          {
            for (k = 0; k <= i - 1; k ++)
                {
                  A[i][j] -= L[i][k] * A[k][j];
                }
          }
      for (i = j + 1; i < n; i ++)
          {
            for (k = 0; k <= j - 1; k ++)
                {
                  A[i][j] -= L[i][k] * A[k][j];
                }
          }
    }
    
     

Acessos à memória

Aqui a matriz A é acessada por coluna e a matriz L é acessada por coluna e por linha. Pode-se considerar um caso médio.

3.4 VERSÃO JKI

Descrição

Nesta versão, para cada coluna j-1, são calculados os valores de L a partir da linha j até a linha n-1. Em seguida é feito o cálculo com os elementos da matriz A a partir da linha 1 até a linha j, calculando para cada linha i o elemento A[i][j], subtraindo deste elemento os elementos da coluna j, linhas de 0 até j-1 da matriz A multiplicados, respectivamente, pelos elementos da linha i, colunas de 0 até j-1 da matriz L (A[i][j] = A[i][j] - L[i][k] * A[k][j]).

Algoritmo

for (j = 1; j < n; j ++)
    {
      for (s = j; s < n; s ++)
          {
            L[s][j-1] = A[s][j-1] / A[j-1][j-1];
          }
      for (k = 0; k <= j - 1; k ++)
          {
            for (i = k + 1; i < n; i ++)
                {
                  A[i][j] -= L[i][k] * A[k][j];
                }
          }
    }
    
     

Acessos à memória

Aqui as duas matrizes são acessadas por coluna, podendo-se considerar como pior caso.

3.5 VERSÃO KIJ

Descrição

Nesta versão, para cada elemento L[i][k] calculado, são calculados os elementos da linha i, colunas de k+1 até n-1 da matriz A, subtraindo-se, respectivamente, os elementos da linha k, colunas de k+1 até n-1 da matriz A multiplicados pelo elemento L[i][k] (A[i][j] = A[i][j] - L[i][k] * A[k][j]).

Algoritmo

for (k = 0; k < n - 1; k ++)
    {
      for (i = k + 1; i < n; i ++)
          {
            L[i][k] = A[i][k] / A[k][k];
            for (j = k + 1; j < n; j ++)
                {
                  A[i][j] -= L[i][k] * A[k][j];
                }
          }
    }
    
     

Acessos à memória

Aqui a matriz A é acessada elemento a elemento de uma coluna e por linha no loop mais interno e, a matriz L é exclusivamente acessada elemento a elemento de uma coluna. Com isso o reuso do cache é garantido e, portanto, o algoritmo pode ser considerado bom.

3.6 VERSÃO KJI

Descrição

Nesta última versão, para cada coluna k, são calculados os elementos da matriz L a partir da linha k+1 até n-1 e, em seguida, são calculados os elementos A[i][j] com i e j variando de k+1 até n-1 (A[i][j] = A[i][j] - L[i][k] * A[k][j]).

Algoritmo

for (k = 0; k < n - 1; k ++)
    {
      for (s = k + 1; s < n; s ++)
          {
            L[s][k] = A[s][k] / A[k][k];
          }
      for (j = k + 1; j < n; j ++)
          {
            for (i = k + 1; i < n; i ++)
                {
                  A[i][j] -= L[i][k] * A[k][j];
                }
          }
    }
    
     

Acessos à memória

Aqui tanto a matriz A quanto a matriz L são exclusivamente acessadas por colunas sendo, portanto, considerado ruim, pois, não garante o reuso do cache.

4 RESULTADOS


4.1 DESEMPENHO DOS ALGORITMOS


Verificamos por esta tabela que o algoritmo com o pior desempenho, em todas as máquinas foi a versão KJI e que a melhor foi a versão KIJ. As medidas foram realizadas usando o comando time( ) da linguagem C, calculando-se apenas o tempo transcorrido para a execução dos loops da decomposição LU, desconsiderando-se as inicializações. Os programas foram compilados com o compilador gcc sem diretivas de otimização. As matrizes usadas foram de dimensões 512 x 512 do tipo float.

Para as medidas na SGI Power Series, foi utilizado o comando runon o qual especifica em qual processador (dos oito existentes) o programa será executado. Isto é necessário para que não ocorra a troca entre os processadores o que causaria perda das informações no cache.

Abaixo, tabelas de comparação de desempenho relativo entre os diversos algoritmos para as três máquinas. Estas tabelas foram obtidas fazendo-se a razão dos tempos de execução dos algoritmos de cada coluna com os tempos de execução dos algoritmos das linhas. Assim, quanto menor o tempo de execução do algoritmo, melhor o seu desempenho.

SUN SPARC 10


SGI INDIGO II


4.2 Gráficos de comparação de desempenho

Gráfico 1 - Desempenho dos Algoritmos


Gráfico 2 - Comparação de Desempenho em Relação a Versão KJI


6 BIBLIOGRAFIA


[1] Carr, S. e Kennedy, K. Compiler Blockability of Numerical Algorithms. In: International Conference in Supercomputing (ICS 92), 1992. Proceedings. p. 114-24.

[2] Golub, G. H. e Loan, C. F. V. "Orthogonalization and Least Squares" em Matrix Computations, 2.ed. John Hopkins University Press, 1989.

[3] Ortega, J. M. The ijk forms of factorization methods I. Vector computers. In: Parallel Computing 7 pp.135-147. North-Holland, 1988.


7 CÓDIGOS FONTE