Qual desses dois métodos é mais eficiente em C? E que tal:
pow(x,3)
vs.
x*x*x // etc?
c++
c
optimization
Jamylak
fonte
fonte
x
integral ou ponto flutuante?Respostas:
Testei a diferença de desempenho entre
x*x*...
vspow(x,i)
para pequenosi
usando este código:Os resultados são:
Observe que eu acumulo o resultado de cada cálculo de potência para garantir que o compilador não o otimize.
Se eu usar a
std::pow(double, double)
versão eloops = 1000000l
, obtenho:Este é um Intel Core Duo rodando Ubuntu 9.10 64bit. Compilado usando gcc 4.4.1 com otimização -o2.
Então em C sim
x*x*x
vai ser mais rápidopow(x, 3)
, porque não hápow(double, int)
sobrecarga. Em C ++, será mais ou menos o mesmo. (Supondo que a metodologia em meu teste esteja correta.)Esta é uma resposta ao comentário feito por An Markm:
Mesmo se uma
using namespace std
diretiva foi emitida, se o segundo parâmetro parapow
for umint
, então astd::pow(double, int)
sobrecarga de<cmath>
será chamada em vez de::pow(double, double)
de<math.h>
.Este código de teste confirma esse comportamento:
fonte
std::pow
8 * tempos de loops (para expoente> 2), a menos que você use-fno-math-errno
. Então, ele pode puxar o pow call para fora do loop, como eu pensei que faria. Eu acho que como errno é global, thread safety requer que ele chame pow para possivelmente definir errno várias vezes ... exp = 1 e exp = 2 são rápidos porque a chamada de pow é içada para fora do loop com apenas-O3
.. ( com - ffast-math , ele faz a soma de 8 fora do loop também.)pow
chamada suspensa do loop, então há uma grande falha aí. Além disso, parece que você está testando principalmente a latência da adição de FP, uma vez que todos os testes são executados no mesmo período de tempo. Você esperariatest5
ser mais lento do quetest1
, mas não é. O uso de vários acumuladores dividiria a cadeia de dependências e ocultaria a latência.pow
a um valor em constante mudança (para evitar que a expressão pow repetida seja içada para fora).Esse é o tipo errado de pergunta. A pergunta certa seria: "Qual é mais fácil de entender para os leitores humanos do meu código?"
Se a velocidade for importante (mais tarde), não pergunte, mas meça. (E antes disso, avalie se otimizar isso realmente fará alguma diferença perceptível.) Até então, escreva o código para que seja mais fácil de ler.
Editar
Só para deixar isso claro (embora já devesse ter sido): acelerações revolucionárias geralmente vêm de coisas como usar algoritmos melhores , melhorar a localidade dos dados , reduzir o uso de memória dinâmica , resultados pré-computacionais , etc. Eles raramente vêm de micro-otimizando chamadas de função única e, onde o fazem, o fazem em poucos lugares , o que só seria encontrado por meio de um perfil cuidadoso (e demorado), mais frequentemente do que nunca, podem ser aceleradas por procedimentos muito não intuitivos coisas (como inserir
noop
), e o que é uma otimização para uma plataforma às vezes é uma pessimização para outra (é por isso que você precisa medir, em vez de perguntar, porque não conhecemos / temos totalmente o seu ambiente).Deixe-me sublinhar isso novamente: mesmo nos poucos aplicativos em que essas coisas importam, elas não importam na maioria dos lugares em que são usadas, e é muito improvável que você encontre os lugares onde elas importam olhando o código. Você realmente precisa identificar os pontos críticos primeiro , porque, do contrário, otimizar o código é apenas uma perda de tempo .
Mesmo que uma única operação (como calcular o quadrado de algum valor) ocupe 10% do tempo de execução do aplicativo (o que IME é bastante raro), e mesmo que otimizá-lo economize 50% do tempo necessário para aquela operação (que IME é muito, muito mais raro), você ainda fez a aplicação demorar apenas 5% menos tempo .
Seus usuários precisarão de um cronômetro para perceber isso. (Eu acho que na maioria dos casos nada menos de 20% speedup passa despercebido para a maioria dos usuários. E que é quatro desses pontos que você precisa encontrar.)
fonte
x*x
oux*x*x
será mais rápido do quepow
, uma vez quepow
deve lidar com o caso geral, enquantox*x
é específico. Além disso, você pode omitir a chamada de função e coisas semelhantes.No entanto, se você estiver micro-otimizando assim, precisará obter um criador de perfil e fazer alguns perfis sérios. A probabilidade esmagadora é que você nunca notaria qualquer diferença entre os dois.
fonte
x*x*x
vs duplostd::pow(double base, int exponent)
em um loop cronometrado e não consigo ver uma diferença de desempenho estatisticamente significativa.Eu também estava me perguntando sobre o problema de desempenho e esperava que isso fosse otimizado pelo compilador, com base na resposta de @EmileCormier. No entanto, eu estava preocupado que o código de teste que ele mostrou ainda permitiria ao compilador otimizar a chamada std :: pow (), uma vez que os mesmos valores eram usados na chamada todas as vezes, o que permitiria ao compilador armazenar os resultados e reutilize-o no loop - isso explicaria os tempos de execução quase idênticos para todos os casos. Então eu também dei uma olhada nisso.
Aqui está o código que usei (test_pow.cpp):
Isso foi compilado usando:
Basicamente, a diferença é que o argumento para std :: pow () é o contador de loop. Como eu temia, a diferença de desempenho é pronunciada. Sem o sinalizador -O2, os resultados em meu sistema (Arch Linux 64-bit, g ++ 4.9.1, Intel i7-4930) foram:
Com a otimização, os resultados foram igualmente impressionantes:
Portanto, parece que o compilador tenta pelo menos otimizar o caso std :: pow (x, 2), mas não o caso std :: pow (x, 3) (leva cerca de 40 vezes mais do que o caso std :: pow (x, 2) caso). Em todos os casos, a expansão manual teve um desempenho melhor - mas particularmente para o case power 3 (60 vezes mais rápido). Isso definitivamente vale a pena ter em mente se executar std :: pow () com potências inteiras maiores que 2 em um loop apertado ...
fonte
A maneira mais eficiente é considerar o crescimento exponencial das multiplicações. Verifique este código para p ^ q:
fonte
Se o expoente for constante e pequeno, expanda-o, minimizando o número de multiplicações. (Por exemplo,
x^4
não é idealx*x*x*x
, masy*y
ondey=x*x
. Ex^5
éy*y*x
ondey=x*x
. E assim por diante.) Para expoentes inteiros constantes, basta escrever a forma otimizada já; com pequenos expoentes, esta é uma otimização padrão que deve ser realizada quer o código tenha sido perfilado ou não. A forma otimizada será mais rápida em uma porcentagem tão grande de casos que basicamente sempre vale a pena fazer.(Se você usar Visual C ++,
std::pow(float,int)
executa a otimização a que aludi, em que a seqüência de operações está relacionada ao padrão de bits do expoente. Não garanto que o compilador irá desenrolar o loop para você, então ainda vale a pena fazer à mão.)[editar] BTW
pow
tem uma tendência (des) surpreendente de aparecer nos resultados do profiler. Se você não precisa absolutamente dele (ou seja, o expoente é grande ou não é uma constante), e você está preocupado com o desempenho, é melhor escrever o código ideal e esperar que o criador de perfil diga que é (surpreendentemente ) perder tempo antes de pensar mais. (A alternativa é chamarpow
e fazer com que o criador de perfil lhe diga que está (sem surpresa) perdendo tempo - você está cortando esta etapa ao fazê-lo de forma inteligente.)fonte
Tenho estado ocupado com um problema semelhante e estou bastante intrigado com os resultados. Eu estava calculando x⁻³ / ² para a gravitação newtoniana em uma situação de n-corpos (aceleração sofrida por outro corpo de massa M situado em um vetor de distância d):
a = M G d*(d²)⁻³/²
(onde d² é o produto de ponto (escalar) de d por si mesmo), e pensei que calcularM*G*pow(d2, -1.5)
seria mais simples do queM*G/d2/sqrt(d2)
O truque é que isso é verdade para sistemas pequenos, mas conforme os sistemas aumentam de tamanho, eles
M*G/d2/sqrt(d2)
se tornam mais eficientes e não entendo por que o tamanho do sistema impacta esse resultado, porque repetir a operação em dados diferentes não afeta. É como se houvesse otimizações possíveis conforme o sistema cresce, mas que não são possíveis compow
fonte