Por que Clang otimiza o loop neste código
#include <time.h>
#include <stdio.h>
static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };
int main()
{
clock_t const start = clock();
for (int i = 0; i < N; ++i) { arr[i] *= 1.0; }
printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}
mas não o loop neste código?
#include <time.h>
#include <stdio.h>
static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };
int main()
{
clock_t const start = clock();
for (int i = 0; i < N; ++i) { arr[i] += 0.0; }
printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}
(Marque C e C ++ porque gostaria de saber se a resposta é diferente para cada uma.)
c++
c
optimization
floating-point
clang
user541686
fonte
fonte
-O3
, não sei como verificar o que isso ativa.static double arr[N]
não é permitido em C;const
variáveis não contam como expressões constantes nessa línguaRespostas:
A norma IEEE 754-2008 para aritmética de ponto flutuante e a norma aritmética independente de idioma (LIA) ISO / IEC 10967, parte 1, respondem por que isso é assim.
O caso da adição
Sob o modo de arredondamento padrão (Round-a-Nearest, Ties-a-Even) , vemos que
x+0.0
produzx
, exceto quandox
é-0.0
: Nesse caso, temos uma soma de dois operandos com sinais opostos cuja soma é zero, e §6.3 parágrafo 3 regras que essa adição produz+0.0
.Como
+0.0
não é bit a bit idêntico ao original-0.0
, e esse-0.0
é um valor legítimo que pode ocorrer como entrada, o compilador é obrigado a inserir o código que transformará potenciais zeros negativos em+0.0
.O resumo: No modo de arredondamento padrão, em
x+0.0
, sex
-0.0
, entãox
ele próprio é um valor de saída aceitável.-0.0
, então o valor de saída deve ser+0.0
, que não é bit a bit idêntico a-0.0
.O caso da multiplicação
No modo de arredondamento padrão , esse problema não ocorre com
x*1.0
. Sex
:x*1.0 == x
sempre.+/- infinity
, então o resultado é+/- infinity
do mesmo sinal.é
NaN
, então de acordo como que significa que o expoente e a mantissa (embora não seja o sinal) de
NaN*1.0
são recomendados para permanecer inalterado em relação à entradaNaN
. O sinal não é especificado de acordo com §6.3p1 acima, mas uma implementação pode especificar que seja idêntico à fonteNaN
.+/- 0.0
, então o resultado é um0
com seu bit de sinal XORed com o bit de sinal de1.0
, de acordo com §6.3p2. Como o bit de sinal de1.0
é0
, o valor de saída é inalterado em relação à entrada. Assim,x*1.0 == x
mesmo quandox
é um zero (negativo).O caso da subtração
No modo de arredondamento padrão , a subtração
x-0.0
também é não operacional, porque é equivalente ax + (-0.0)
. Sex
éNaN
, então, §6.3p1 e §6.2.3 se aplicam da mesma maneira que para adição e multiplicação.+/- infinity
, então o resultado é+/- infinity
do mesmo sinal.x-0.0 == x
sempre.-0.0
, então, em §6.3p2, temos " [...] o sinal de uma soma, ou de uma diferença x - y considerada uma soma x + (−y), difere de no máximo um dos sinais dos adendos; " Isso nos obriga a atribuir-0.0
como resultado de(-0.0) + (-0.0)
, porque-0.0
difere no sinal de nenhum dos adendos, enquanto+0.0
difere no sinal de dois dos adendos, violando esta cláusula.+0.0
, então, isso se reduz ao caso de adição(+0.0) + (-0.0)
considerado acima em The Case of Addition , que por §6.3p3 está decidido a dar+0.0
.Como em todos os casos o valor de entrada é legal como saída, é permitido considerar
x-0.0
uma no-op ex == x-0.0
uma tautologia.Otimizações de mudança de valor
A norma IEEE 754-2008 possui a seguinte citação interessante:
Como todos os NaNs e todos os infinitos compartilham o mesmo expoente, e o resultado arredondado corretamente de
x+0.0
ex*1.0
para finitox
tem exatamente a mesma magnitude quex
, seu expoente é o mesmo.sNaNs
NaNs de sinalização são valores de interceptação de ponto flutuante; São valores especiais de NaN cujo uso como um operando de ponto flutuante resulta em uma exceção de operação inválida (SIGFPE). Se um loop que desencadeia uma exceção fosse otimizado, o software não se comportaria mais da mesma maneira.
No entanto, como o usuário2357112 aponta nos comentários , o Padrão C11 deixa explicitamente indefinido o comportamento dos NaNs de sinalização (
sNaN
), de modo que o compilador pode assumir que eles não ocorrem e, portanto, as exceções que eles geram também não ocorrem. O padrão C ++ 11 omite a descrição de um comportamento para sinalizar NaNs e, portanto, também o deixa indefinido.Modos de arredondamento
Nos modos alternativos de arredondamento, as otimizações permitidas podem mudar. Por exemplo, no modo Arredondar para Infinito Negativo , a otimização
x+0.0 -> x
se torna permitida, masx-0.0 -> x
é proibida.Para impedir que o GCC assuma os modos e comportamentos padrão de arredondamento, o sinalizador experimental
-frounding-math
pode ser passado para o GCC.Conclusão
Clang e GCC , mesmo em
-O3
, permanecem em conformidade com a IEEE-754. Isso significa que ele deve seguir as regras acima do padrão IEEE-754. nãox+0.0
é nem um pouco idêntico ax
todos, dex
acordo com essas regras, masx*1.0
pode ser escolhido assim : ou seja, quandox
quando é um NaN.* 1.0
.x
for um NaN.Para ativar a otimização não segura IEEE-754
(x+0.0) -> x
, o sinalizador-ffast-math
precisa ser passado para Clang ou GCC.fonte
x += 0.0
não é um NOOP sex
é-0.0
. O otimizador pode remover todo o loop de qualquer maneira, pois os resultados não são usados. Em geral, é difícil dizer por que um otimizador toma as decisões que toma.fonte
x += 0.0
não ser um não-op, mas pensei que provavelmente não fosse esse o motivo, porque todo o loop deveria ser otimizado de qualquer maneira. Posso comprá-lo, é só não tão inteiramente convincente como eu estava esperando ...long long
a otimização está em vigor (o fez com o gcc, que se comporta da mesma forma pelo menos duas vezes )long long
é um tipo integral, não um tipo IEEE754.x -= 0
, é o mesmo?