Realmente otimizando C++

Otimização Final – g++

No post Quer velocidade no C++? Faça a coisa certa…, mostrei como podemos ajudar o otimizador do g++ sem escovar bits. Agora, mostro que tipo de escovação de bits é aceitável, e produz bons resultados. Note que ainda assim, o tempo final continua maior que o do compilador Intel.

Basicamente, podemos ajudar o compilador a identificar partes dos loops que contém dados invariantes. O novo programa, ao invés de ter os três loops aninhados, possui uma chamada a uma função para fazer a multiplicação por linha. No entanto, essa função é inline, de modo que é expandida no código. A princípio, isso não deveria melhorar o desempenho, porém o fato de passarmos parâmetros dá uma dica importante ao compilador: os parâmetros não mudam. Nem sempre o otimizador consegue descobrir isso.

#include <cstdlib>

const int N = 1000;
typedef double Linha[N];

inline void multiplicaPorLinha( Linha& ci, const double aik, const Linha& bk ) {
  for( int j = 0; j < N; j++ )
    ci[j] += aik * bk[j];
}

int main(int argc, char *argv[])
{
  Linha * const a = new Linha[N],
        * const b = new Linha[N],
        * const c = new Linha[N];

  srand(1);
  for( int i = 0; i < N; i++ )
    for( int j = 0; j < N; j++ ) {
      a[i][j] = rand()/(rand()+0.1);
      b[i][j] = rand()/(rand()+0.1);
      c[i][j] = 0;
    }

  for( int i = 0; i < N; i++ )
    for( int k = 0; k < N; k++ )
      multiplicaPorLinha( c[i], a[i][k], b[k] );

  delete [] a;
  delete [] b;
  delete [] c;
}

Essa otimização sozinha já reduz o tempo de 2.79 segundos para 2.22 segundos. Mas além disso, se estivermos amarrados ao Dev-Cpp, podemos trocar a linha de comando para otimizar para o K8 ao invés do Pentium 4 – o motivo é que quase todas as novas CPUs, mesmo as Intel, são compatíveis com o K8, e o código que o g++ gera para o K8 é melhor que o gerado para o Pentium 4, especialmente por causa do conjunto de instruções sse3. Linha de comando:

-O3 -funroll-loops -fprefetch-loop-arrays -march=k8 -msse3

Resultado: 1.80 segundos. Por último, se você instalar o g++ 4.40 (para Windows, MingW), pode usar a seguinte linha de argumentos:

-O3 -funroll-loops -fprefetch-loop-arrays -march=core2 -msse4

Agora sim para Intel Core 2. Resultado: 1.62 segundos… 10x mais rápido que o programa com opção -O3, 1.62 x mais rápido que o post anterior, e 11% mais rápido que para o K8.

Infelizmente essa otimização faz com que o compilador Intel perca umpouco de desempenho: agora fica com 1.2 segundos, o dobro do tempo anterior, embora ainda mais rápido que o g++. Não consegui descobrir porque isso ocorreu… :(

Rio 2016!!!!

Bookmark and Share

Post to Twitter

Leave a Reply