Por que é x**4.0
mais rápido que x**4
no Python 3 * ?
Os int
objetos 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_digit
array 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 PyLongObject
s.)
Os float
objetos Python , pelo contrário, podem ser transformados em um double
tipo 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 iv
e quais iw
são os nossos PyFloatObject
s originais como C double
s.
Pelo que vale a pena: Python 2.7.13
para mim é um fator 2~3
mais 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 int
objeto antigo que difere do int
objeto no Python 3 (todos os int
objetos no 3.x são do PyLongObject
tipo). 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 float
s fazer , ele é convertido com segurança em um C long
quando exponenciação é realizada sobre ele (o int_pow
també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 x
nome em uma long
chamada no Python 2 (essencialmente forçando-o a usar long_pow
como 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 int
to long
enquanto 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_CONST
carregamento do float em 256.0
vez 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.