Eficiente 10 elevado ao dobro da potência

Oct 22 2020

Eu tenho que aumentar 10 à potência de um duplo muitas vezes.

Existe uma maneira mais eficiente de fazer isso do que com a biblioteca de matemática pow(10,double)? Se for importante, meus duplos são sempre negativos entre -5 e -11.

Presumo que pow (double, double) use um algoritmo mais geral do que o necessário para pow (10, double) e pode, portanto, não ser o método mais rápido. Dadas algumas das respostas abaixo, essa pode ter sido uma suposição incorreta.

Quanto ao porquê, é para interpolação logartímica. Eu tenho uma tabela de valores xey. Meu objeto tem um valor x conhecido (que quase sempre é duplo).

double Dbeta(struct Data *diffusion, double per){
  double frac;
  while(per>diffusion->x[i]){
      i++;
  }
  frac = (per-diffusion->x[i-1])/(diffusion->x[i]-diffusion->x[i-1]);
  return pow(10,log10DB[i-1] + frac * (log10DB[i]-log10DB[i-1]));
}

Esta função é chamada muitas vezes. Disseram-me para analisar o perfil, então é o que farei primeiro.

Acabei de saber que poderia ter usado logaritmos naturais em vez da base 10, o que é obviamente correto. (minha estupidez às vezes surpreende até a mim mesmo.)

Depois de substituir tudo por logaritmos naturais, tudo funciona um pouco mais rápido. Com profiling (que é uma palavra nova que aprendi hoje), descobri que 39% do meu código é gasto na função exp, então, para aqueles que se perguntaram se era de fato essa parte que estava estrangulando meu código, era.

Respostas

3 PascalGetreuer Oct 22 2020 at 14:58

Sim, a função pow é lenta (cerca de 50 vezes o custo de uma multiplicação, para quem pede benchmarks).

  • Por algum truque de log / expoentes, podemos expressar 10 ^ x como

    10^x = exp(log(10^x)) = exp(x * log(10)).
    

    Portanto, você pode implementar 10 ^ x com exp(x * M_LN10), que deve ser mais eficiente do que pow.

  • Se a precisão dupla não for crítica, use a versão flutuante da função expf(ou powf), que deve ser mais eficiente do que a versão dupla.

  • Se a precisão aproximada estiver OK, pré-calcule uma tabela na faixa [-5, -11] e faça uma pesquisa rápida com interpolação linear.

Alguns benchmarks (usando glibc 2.31):

Benchmark                Time
---------------------------------
pow(10, x)               15.54 ns
powf(10, x)               7.18 ns
expf(x * (float)M_LN10)   3.45 ns
5 TomKarzes Oct 22 2020 at 14:53

Pois pow(10.0, n)deve ser mais rápido definir c = log(10.0), que você pode calcular uma vez e, em seguida, usar exp(c*n), que deve ser significativamente mais rápido do que pow(10.0, n)(o que é basicamente fazer a mesma coisa internamente, exceto que seria calcular log(10.0)repetidamente em vez de apenas uma vez). Além disso, provavelmente não há muito mais que você possa fazer.