Perché è x**4.0
più veloce che x**4
in Python 3 * ?
Gli int
oggetti Python 3 sono un oggetto completo progettato per supportare una dimensione arbitraria; per questo motivo, vengono gestiti come tali a livello C (vedere come tutte le variabili vengono dichiarate come PyLongObject *
tipo in long_pow
). Questo rende anche la loro esponenziazione molto più complicata e noiosa poiché è necessario giocare con l' ob_digit
array che utilizza per rappresentare il suo valore per eseguirlo. ( Sorgente per i più coraggiosi. Vedi: Comprensione dell'allocazione di memoria per interi di grandi dimensioni in Python per ulteriori informazioni su PyLongObject
s.)
Gli float
oggetti Python , al contrario, possono essere trasformati in un double
tipo C (usando PyFloat_AsDouble
) e le operazioni possono essere eseguite usando quei tipi nativi . Questo è fantastico perché, dopo aver verificato i casi limite rilevanti, consente a Python di utilizzare le piattaformepow
( C pow
, cioè ) per gestire l'esponenziale effettivo:
/* 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);
dove iv
e iw
sono i nostri originali PyFloatObject
come C double
s.
Per quello che vale: Python 2.7.13
per me è un fattore 2~3
più veloce e mostra il comportamento inverso.
Il fatto precedente spiega anche la discrepanza tra Python 2 e 3, quindi ho pensato di affrontare anche questo commento perché è interessante.
In Python 2, stai usando il vecchio int
oggetto che differisce int
dall'oggetto in Python 3 (tutti gli int
oggetti in 3.x sono di PyLongObject
tipo). In Python 2, c'è una distinzione che dipende dal valore dell'oggetto (o, se si utilizza il suffisso L/l
):
# Python 2
type(30) # <type 'int'>
type(30L) # <type 'long'>
Il <type 'int'>
che vedete qui fa la stessa cosa float
s lo fanno , si ottiene in modo sicuro trasformata in C long
quando elevamento a potenza viene eseguita su di esso (Il int_pow
accenna anche al compilatore di mettere 'em in un registro, se può farlo, in modo che possa fare la differenza) :
static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */
questo consente un buon guadagno di velocità.
Per vedere come le <type 'long'>
s lente sono in confronto a <type 'int'>
s, se hai racchiuso il x
nome in una long
chiamata in Python 2 (essenzialmente costringendolo a usarlo long_pow
come in Python 3), il guadagno di velocità scompare:
# <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
Prendi nota che, sebbene uno snippet trasformi il int
to long
mentre l'altro no (come sottolineato da @pydsinger), questo cast non è la forza che contribuisce al rallentamento. L'implementazione di long_pow
è. (Tempo le dichiarazioni solo con long(x)
per vedere).
[...] non accade al di fuori del ciclo. [...] Qualche idea a riguardo?
Questo è l'ottimizzatore spioncino di CPython che piega le costanti per te. Si ottengono gli stessi tempi esatti in entrambi i casi poiché non esiste un calcolo effettivo per trovare il risultato dell'espiazione, solo caricamento di valori:
dis.dis(compile('4 ** 4', '', 'exec'))
1 0 LOAD_CONST 2 (256)
3 POP_TOP
4 LOAD_CONST 1 (None)
7 RETURN_VALUE
Il codice byte identico viene generato per '4 ** 4.'
con l'unica differenza che LOAD_CONST
carica il float 256.0
anziché 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
Quindi i tempi sono identici.
* Tutto quanto sopra si applica esclusivamente a CPython, l'implementazione di riferimento di Python. Altre implementazioni potrebbero funzionare diversamente.