para - compilar gcc terminal windows




Por que o GCC não otimiza a*a*a*a*a*a a(a*a*a)*(a*a*a)? (8)

Como Lambdageek apontou, a multiplicação de float não é associativa e você pode obter menos precisão, mas também quando consegue uma precisão melhor, você pode argumentar contra a otimização, porque você quer uma aplicação determinística. Por exemplo, em simulação de jogo cliente / servidor, onde cada cliente tem que simular o mesmo mundo que você quer que os cálculos de ponto flutuante sejam determinísticos.

Eu estou fazendo alguma otimização numérica em uma aplicação científica. Uma coisa que eu notei é que o GCC vai otimizar o call pow(a,2) compilando-o em a*a , mas o call pow(a,6) não é otimizado e vai chamar a função de biblioteca pow , o que diminui bastante o desempenho. (Em contraste, o Intel C ++ Compiler , icc executável, eliminará a chamada da biblioteca para pow(a,6) .)

O que eu estou curioso é que quando eu substitui pow(a,6) com a*a*a*a*a*a usando o GCC 4.5.1 e opções " -O3 -lm -funroll-loops -msse4 ", ele usa 5 instruções mulsd :

movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13

enquanto se eu escrever (a*a*a)*(a*a*a) , ele produzirá

movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm13, %xmm13

que reduz o número de instruções múltiplas para 3. icc tem comportamento similar.

Por que os compiladores não reconhecem esse truque de otimização?


Eu não esperava que esse caso fosse otimizado. Não é muito comum que uma expressão contenha subexpressões que podem ser reagrupadas para remover operações inteiras. Eu esperaria que os escritores de compiladores investissem seu tempo em áreas com maior probabilidade de resultar em melhorias perceptíveis, em vez de cobrir um caso de borda raramente encontrado.

Fiquei surpreso ao aprender com as outras respostas que essa expressão poderia de fato ser otimizada com as opções adequadas do compilador. Ou a otimização é trivial, ou é um caso extremo de uma otimização muito mais comum, ou os escritores do compilador eram extremamente completos.

Não há nada de errado em fornecer dicas para o compilador como você fez aqui. É uma parte normal e esperada do processo de micro-otimização para reorganizar declarações e expressões para ver quais as diferenças que eles trarão.

Embora o compilador possa ser justificado ao considerar as duas expressões para fornecer resultados inconsistentes (sem os comutadores apropriados), não há necessidade de você estar vinculado a essa restrição. A diferença será incrivelmente pequena - tanto que se a diferença for importante para você, você não deveria estar usando aritmética de ponto flutuante padrão em primeiro lugar.


Já existem algumas boas respostas a esta questão, mas para completar, gostaria de salientar que a seção aplicável do padrão C é 5.1.2.2.3 / 15 (que é a mesma que a seção 1.9 / 9 no Padrão C ++ 11). Esta seção afirma que os operadores só podem ser reagrupados se forem realmente associativos ou comutativos.


Nenhum pôster mencionou a contração de expressões flutuantes ainda (padrão ISO C, 6.5p8 e 7.12.2). Se o pragma FP_CONTRACT estiver definido como ON , o compilador poderá considerar uma expressão como a*a*a*a*a*a como uma única operação, como se avaliada exatamente com um único arredondamento. Por exemplo, um compilador pode substituí-lo por uma função de energia interna que seja mais rápida e precisa. Isso é particularmente interessante, pois o comportamento é parcialmente controlado pelo programador diretamente no código-fonte, enquanto as opções do compilador fornecidas pelo usuário final podem às vezes ser usadas incorretamente.

O estado padrão do pragma FP_CONTRACT é definido pela implementação, de modo que um compilador pode fazer essas otimizações por padrão. Portanto, o código portátil que precisa seguir estritamente as regras do IEEE 754 deve explicitamente defini-lo como OFF .

Se um compilador não suportar esse pragma, ele deve ser conservador, evitando qualquer otimização, caso o desenvolvedor tenha escolhido configurá-lo como OFF .

O GCC não suporta esse pragma, mas com as opções padrão, ele assume que ele está ON ; assim, para alvos com um FMA de hardware, se alguém quiser impedir a transformação de a*b+c para fma (a, b, c), é necessário fornecer uma opção como -ffp-contract=off (para definir explicitamente o pragma para OFF ) ou -std=c99 (para informar ao GCC que está de acordo com alguma versão padrão C, aqui C99, siga o parágrafo acima). No passado, a última opção não estava impedindo a transformação, o que significa que o GCC não estava em conformidade neste ponto: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37845


O GCC realmente otimiza a * a * a * a * a * a para (a * a * a) * (a * a * a) quando a é um inteiro. Eu tentei com este comando:

$ echo 'int f(int x) { return x*x*x*x*x*x; }' | gcc -o - -O2 -S -masm=intel -x c -

Há muitas flags do gcc, mas nada extravagante. Eles significam: Leia de stdin; use o nível de otimização de O2; listagem de linguagem de montagem de saída em vez de um binário; a listagem deve usar a sintaxe da linguagem assembly da Intel; a entrada está na linguagem C (geralmente a linguagem é inferida da extensão de arquivo de entrada, mas não há extensão de arquivo ao ler a partir de stdin); e escreva para stdout.

Aqui está a parte importante da saída. Anotei com alguns comentários indicando o que está acontecendo na linguagem assembly:

    ; x is in edi to begin with.  eax will be used as a temporary register.
    mov    eax, edi     ; temp1 = x
    imul    eax, edi    ; temp2 = x * temp1
    imul    eax, edi    ; temp3 = x * temp2
    imul    eax, eax    ; temp4 = temp3 * temp3

Eu estou usando o sistema GCC no Linux Mint 16 Petra, um derivado do Ubuntu. Aqui está a versão do gcc:

$ gcc --version
gcc (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1

Como outros pôsteres notaram, esta opção não é possível em ponto flutuante, porque a aritmética de ponto flutuante não é realmente associativa.


O gcc realmente pode fazer essa otimização, mesmo para números de ponto flutuante. Por exemplo,

double foo(double a) {
  return a*a*a*a*a*a;
}

torna-se

foo(double):
    mulsd   %xmm0, %xmm0
    movapd  %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
    mulsd   %xmm1, %xmm0
    ret

com -O -funsafe-math-optimizations . Esta reordenação viola a IEEE-754, por isso requer a bandeira.

Inteiros assinados, como Peter Cordes apontou em um comentário, podem fazer essa otimização sem -funsafe-math-optimizationsque ela seja mantida exatamente quando não houver estouro e, se houver estouro, você tiver um comportamento indefinido. Então você começa

foo(long):
    movq    %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rax, %rax
    ret

com apenas -O. Para inteiros não assinados, é ainda mais fácil, pois eles trabalham com potências mod de 2 e, portanto, podem ser reordenados livremente, mesmo em face do estouro.


Porque a matemática do ponto flutuante não é associativa . A maneira como você agrupa os operandos na multiplicação de ponto flutuante afeta a exatidão numérica da resposta.

Como resultado, a maioria dos compiladores é muito conservadora sobre a reordenação de cálculos de ponto flutuante, a menos que eles possam ter certeza de que a resposta permanecerá a mesma, ou a menos que você diga que não se importa com exatidão numérica. Por exemplo: a opção -fassociative-math do gcc que permite ao gcc reassociar operações de ponto flutuante, ou até mesmo a opção -ffast-math que permite compensações ainda mais agressivas de precisão em relação à velocidade.


Porque um número de ponto flutuante de 32 bits - como 1.024 - não é 1.024. Em um computador, 1.024 é um intervalo: de (1.024-e) para (1.024 + e), em que "e" representa um erro. Algumas pessoas não conseguem perceber isso e também acreditam que * em a * a significa multiplicação de números de precisão arbitrária sem que haja erros associados a esses números. A razão pela qual algumas pessoas não conseguem perceber isso é, talvez, os cálculos matemáticos que eles exerceram nas escolas elementares: trabalhando apenas com números ideais, sem erros anexados, e acreditando que é OK simplesmente ignorar "e" enquanto se realiza multiplicação. Eles não vêem o "e" implícito em "float a = 1.2", "a * a * a" e códigos C semelhantes.

Caso a maioria dos programadores reconheça (e seja capaz de executar) a ideia de que a expressão C a * a * a * a * a * a não está realmente trabalhando com números ideais, o compilador GCC estaria então LIVRE para otimizar "a * a * a * a * a * a "em dizer" t = (a * a); t * t * t "que requer um menor número de multiplicações. Mas, infelizmente, o compilador do GCC não sabe se o programador que está escrevendo o código pensa que "a" é um número com ou sem erro. E assim, o GCC só fará o que o código-fonte parece - porque é isso que o GCC vê com o seu olho nu.

... uma vez que você sabe que tipo de programador você é, você pode usar a opção "-ffast-math" para dizer ao GCC que "Ei, GCC, eu sei o que estou fazendo!". Isso permitirá que o GCC converta um * a * a * a * a * a em um pedaço de texto diferente - ele parece diferente de um * a * a * a * a * a - mas ainda calcula um número dentro do intervalo de erro de a * a * a * a * a * a. Tudo bem, já que você já sabe que está trabalhando com intervalos, não com números ideais.





fast-math