Mais dicas para ponto flutuante: Use FMA, se tiver!

Depois de tantos artigos que escrevi sobre ponto flutuante aqui acredito que uma coisa já deve estar clara: Ponto flutuante não é exato! Erros de arredondamento fazem parte do jogo e você tem que saber como eles funcionam para que suas rotinas não façam as contas de forma errada.

Cada operação aritmética pode acrescentar um errinho. Existem poucos casos onde a conta pode ser feita de forma exata! Assim, quanto menos erros, melhor, certo? Mas, o que acontece se você tiver que fazer algo como “d = a * b + c;“? Note: Temos duas operações e a adição de dois erros de arredondamento prováveis. Pior ainda: A multiplicação pode acrescentar um erro maior que a adição… Como minimizar isso?

Existem classes de instruções especiais chamadas Fused Multiply and Add, ou FMA, para os íntimos. Seu processador pode fazer duas contas em ponto flutuante de uma só vez, uma multiplicação e uma adição (ou subtração), e usando SSE ou AVX! Ele usará Instruções como essas três:

VFMADD132SD xmm0,xmm1,xmm2 ; xmm0=xmm0*xmm3+xmm1
VFMADD213SD xmm0,xmm1,xmm2 ; xmm0=xmm1*xmm0+xmm2
VFMADD231SD xmm0,xmm1,xmm2 ; xmm0=xmm1*xmm2+xmm0

Os valores 132, 213 e 231 são, obviamente, relativos a ordem com que a multiplicação e a soma serão feitas… Existem as instruções VFMASUBnnnSD também. Como de praxe, os sufixos SS, SD, PS e PD indicam a precisão e a quantidade de operadores contidos nos registradores XMM.

Como descobrir se seu processador tem FMA? De duas formas: Olhando /proc/cpuinfo no seu Linux:

$ cat /proc/cpuinfo | grep -o fma | uniq
fma

Com esse pequeno comando e alguma adaptação num Makefile você pode determinar se pode ou não adicionar a opção de compilação “-mfma” na linha de comando do GCC. Existe também uma opção “-mfma4“, mas ela só é válida para processadores AMD.
A outra forma, se quiser incorporar no seu código, é através da instrução CPUID:

int is_fma_available(void)
{
  int avail = 0;

  __asm__ __volatile__ (
    "movl $1,%%eax\n"
    "cpuid\n"
    "andl  $0x1000,%%ecx\n"
    "movl %%ecx,%0"
    : "=g" (avail)
  );
}

FMA está disponível em todo processador de arquitetura Haswell, pelo menos.

PS: FMA não é um troço novo! Existe há décadas, desde os antigos DEC VAX. E, para melhorar ainda mais a história, faz parte do padrão IEEE 754 desde 2008 e, desde a versão ISO C99, os compiladores C possuem funções específicas para lidar com FMA no header math.h. Procure por fmaf(), fma() e fmal() para floats, doubles e long doubles (se bem que esse último não deve funcionar bem!).

E imprescindível que você informe ao compilador que use as instruções disponíveis no processador via opção “-mfma” e, no caso do modo i386, para funcionar bem, também com as opções “-mfpmath=sse” e “-msse2“, pelo menos.

Anúncios