O que o ffast-math do gcc realmente faz?

144

Entendo que o --ffast-mathsinalizador do gcc pode aumentar muito a velocidade das operações flutuantes e fica fora dos padrões do IEEE, mas não consigo encontrar informações sobre o que realmente está acontecendo quando está ligado. Alguém pode explicar alguns detalhes e talvez dar um exemplo claro de como algo mudaria se a bandeira estivesse ligada ou desligada?

Tentei pesquisar no SO para perguntas semelhantes, mas não consegui encontrar nada que explicasse o funcionamento do ffast-math.

Ponml
fonte

Respostas:

86

Como você mencionou, ele permite otimizações que não preservam a conformidade estrita com o IEEE.

Um exemplo é este:

x = x*x*x*x*x*x*x*x;

para

x *= x;
x *= x;
x *= x;

Como a aritmética de ponto flutuante não é associativa, a ordenação e fatoração das operações afetarão os resultados devido ao arredondamento. Portanto, essa otimização não é feita sob o comportamento estrito do FP.

Na verdade, não verifiquei se o GCC realmente faz essa otimização específica. Mas a ideia é a mesma.

Mysticial
fonte
25
@Andrey: Para este exemplo, você vai de 7 multiplica até 3.
Mysticial
4
@ Andy: Matematicamente, estará correto. Mas o resultado pode diferir ligeiramente nos últimos bits devido aos diferentes arredondamentos.
Mysticial 14/09/11
1
Na maioria dos casos, essa pequena diferença não importa (relativamente na ordem de 10 ^ -16 para double, mas varia de acordo com a aplicação). Uma coisa a observar é que as otimizações rápidas de matemática não adicionam necessariamente "mais" arredondamentos. A única razão pela qual não é compatível com IEEE é porque a resposta é diferente (embora um pouco) do que está escrito.
Mysticial 14/09/11
1
@ usuário: A magnitude do erro depende dos dados de entrada. Deve ser pequeno em relação ao resultado. Por exemplo, se xfor menor que 10, o erro no exemplo do Mystical será reduzido em torno de 10 ^ -10. Mas se x = 10e20, é provável que o erro seja muitos milhões.
Ben Voigt
3
@stefanct é realmente sobre -fassociative-matho que está incluído na -funsafe-math-optimizationsque por sua vez é ativado com -ffast-math Por que não GCC otimizar a*a*a*a*a*aa (a*a*a)*(a*a*a)?
Phuclv
255

-ffast-math faz muito mais do que apenas quebrar a conformidade estrita com o IEEE.

Antes de tudo, é claro, ele quebra a estrita conformidade com o IEEE, permitindo, por exemplo, reordenar as instruções para algo que é matematicamente o mesmo (idealmente), mas não exatamente o mesmo no ponto flutuante.

Segundo, desabilita a configuração errnoapós funções matemáticas de instrução única, o que significa evitar a gravação em uma variável local de encadeamento (isso pode fazer uma diferença de 100% para essas funções em algumas arquiteturas).

Terceiro, assume-se que toda a matemática é finita , o que significa que nenhuma verificação de NaN (ou zero) é feita no lugar onde eles teriam efeitos prejudiciais. Supõe-se simplesmente que isso não vai acontecer.

Quarto, permite aproximações recíprocas para divisão e raiz quadrada recíproca.

Além disso, ele desativa o zero assinado (o código assume que o zero assinado não existe, mesmo que o destino o suporte) e a matemática de arredondamento, que permite, entre outras coisas, dobrar constantemente no tempo de compilação.

Por fim, gera código que pressupõe que nenhuma interrupção de hardware possa ocorrer devido à matemática de sinalização / interceptação (ou seja, se elas não puderem ser desabilitadas na arquitetura de destino e, consequentemente , acontecerem , elas não serão tratadas).

Damon
fonte
15
Damon, obrigado! Você pode adicionar algumas referências? Como gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html " -ffast-math Define -fno-math-errno, -funsafe-math-optimizations, -ffinite-only-math, -fno-rounding-math, -fno-signaling -nans e -fcx-limited-range. Esta opção faz com que a macro FAST_MATH do pré-processador seja definida. "e algo da glibc, como ( math.hperto de math_errhandling)" Por padrão, todas as funções suportam manipulação de errno e exceção. No modo matemático rápido do gcc e se funções embutidas são definidas, isso pode não ser verdadeiro. "
osgx 03/03
4
@ javapowered: se é "perigoso" depende das garantias de que você precisa. -ffast-mathpermite ao compilador cortar alguns cantos e quebrar algumas promessas (como explicado), o que geralmente não é perigoso como tal e não é um problema para a maioria das pessoas. Para a maioria das pessoas, é o mesmo, apenas mais rápido. No entanto, se o seu código assumir e confiar nessas promessas, ele poderá se comportar de maneira diferente do esperado. Geralmente, isso significa que o programa parece funcionar bem, principalmente, mas alguns resultados podem ser "inesperados" (digamos, em uma simulação de física, dois objetos podem não colidir adequadamente).
Damon
2
@Royi: Os dois devem ser independentes um do outro. -O2geralmente permite "toda" otimização legal, exceto aquelas que trocam tamanho por velocidade. -O3também permite otimizações que trocam tamanho por velocidade. Ele ainda mantém 100% de correção. -ffast-mathtenta tornar as operações matemáticas mais rápidas, permitindo um comportamento "levemente incorreto", o que geralmente não é prejudicial, mas seria considerado incorreto pela redação do padrão. Se o código é realmente muito diferente em velocidade em dois compiladores (não apenas 1-2%), em seguida, verificar se o seu código é compatível rigorosamente as normas e ...
Damon
1
... produz zero avisos. Além disso, certifique-se de não atrapalhar as regras de alias e coisas como vetorização automática. Em princípio, o GCC deve ter um desempenho tão bom (geralmente melhor na minha experiência) quanto o MSVC. Quando esse não é o caso, você provavelmente cometeu um erro sutil que a MSVC simplesmente ignora, mas que faz com que o GCC desative uma otimização. Você deve dar as duas opções se quiser as duas, sim.
Damon
1
@Royi: Esse código não parece muito pequeno e simples para mim, não é algo que se possa analisar em profundidade em alguns minutos (ou até horas). Entre outras coisas, envolve um processo aparentemente inofensivo #pragma omp parallel fore, dentro do corpo do loop, você está lendo e escrevendo em endereços apontados por argumentos de função e realiza uma ramificação não trivial. Como um palpite sem instrução, você pode estar debulhando caches de dentro de sua chamada de threads definida pela implementação, e o MSVC pode evitar incorretamente repositórios intermediários que as regras de aliasing exigiriam. Impossível dizer.
Damon