Por que é x**4.0 mais rápido que x**4no Python 3 * ?
Os intobjetos Python 3 são um objeto completo desenvolvido para suportar um tamanho arbitrário; devido a esse fato, elas são tratadas como tal no nível C (veja como todas as variáveis são declaradas como PyLongObject *tipo long_pow). Isso também torna a exponenciação muito mais complicada e tediosa, pois você precisa brincar com o ob_digitarray que ele usa para representar seu valor para executá-lo. ( Fonte para os corajosos. - Consulte: Entendendo a alocação de memória para números inteiros grandes em Python para obter mais informações sobre PyLongObjects.)
Os floatobjetos Python , pelo contrário, podem ser transformados em um doubletipo C (usando PyFloat_AsDouble) e as operações podem ser executadas usando esses tipos nativos . Isso é ótimo , porque, após a verificação de borda casos relevantes, permite Python para usar as plataformaspow ( de C pow, que é ) para lidar com a exponenciação real:
/* Now iv and iw are finite, iw is nonzero, and iv is
* positive and not equal to 1.0. We finally allow
* the platform pow to step in and do the rest.
*/
errno = 0;
PyFPE_START_PROTECT("pow", return NULL)
ix = pow(iv, iw);
Onde ive quais iwsão os nossos PyFloatObjects originais como C doubles.
Pelo que vale a pena: Python 2.7.13para mim é um fator 2~3mais rápido e mostra o comportamento inverso.
O fato anterior também explica a discrepância entre Python 2 e 3, então, pensei em abordar esse comentário também porque é interessante.
No Python 2, você está usando o intobjeto antigo que difere do intobjeto no Python 3 (todos os intobjetos no 3.x são do PyLongObjecttipo). No Python 2, há uma distinção que depende do valor do objeto (ou, se você usar o sufixo L/l):
# Python 2
type(30) # <type 'int'>
type(30L) # <type 'long'>
O <type 'int'>que você vê aqui faz a mesma coisa floats fazer , ele é convertido com segurança em um C long quando exponenciação é realizada sobre ele (o int_powtambém sugere que o compilador para colocá-los num registo se ele pode fazê-lo, de modo que poderia fazer a diferença) :
static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */
isso permite um bom ganho de velocidade.
Para ver como <type 'long'>s são lentos em comparação com <type 'int'>s, se você colocar o xnome em uma longchamada no Python 2 (essencialmente forçando-o a usar long_powcomo no Python 3), o ganho de velocidade desaparece:
# <type 'int'>
(python2) ➜ python -m timeit "for x in range(1000):" " x**2"
10000 loops, best of 3: 116 usec per loop
# <type 'long'>
(python2) ➜ python -m timeit "for x in range(1000):" " long(x)**2"
100 loops, best of 3: 2.12 msec per loop
Observe que, embora um trecho transforme o intto longenquanto o outro não (como apontado por @pydsinger), esse elenco não é a força que contribui por trás da desaceleração. A implementação de long_powé. (Cronometre as declarações apenas com long(x)para ver).
[...] isso não acontece fora do loop. [...] tem ideia disso?
Este é o otimizador de olho mágico do CPython, dobrando as constantes para você. Você obtém os mesmos tempos exatos em ambos os casos, pois não há computação real para encontrar o resultado da exponenciação, apenas o carregamento de valores:
dis.dis(compile('4 ** 4', '', 'exec'))
1 0 LOAD_CONST 2 (256)
3 POP_TOP
4 LOAD_CONST 1 (None)
7 RETURN_VALUE
O código de byte idêntico é gerado, '4 ** 4.'com a única diferença: o LOAD_CONSTcarregamento do float em 256.0vez do int 256:
dis.dis(compile('4 ** 4.', '', 'exec'))
1 0 LOAD_CONST 3 (256.0)
2 POP_TOP
4 LOAD_CONST 2 (None)
6 RETURN_VALUE
Então os tempos são idênticos.
* Todas as opções acima se aplicam apenas ao CPython, a implementação de referência do Python. Outras implementações podem ter um desempenho diferente.