Perché è x**4.0 più veloce che x**4in Python 3 * ?
Gli intoggetti 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_digitarray 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 PyLongObjects.)
Gli floatoggetti Python , al contrario, possono essere trasformati in un doubletipo 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 ive iwsono i nostri originali PyFloatObjectcome C doubles.
Per quello che vale: Python 2.7.13per me è un fattore 2~3più 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 intoggetto che differisce intdall'oggetto in Python 3 (tutti gli intoggetti in 3.x sono di PyLongObjecttipo). 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 floats lo fanno , si ottiene in modo sicuro trasformata in C long quando elevamento a potenza viene eseguita su di esso (Il int_powaccenna 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 xnome in una longchiamata in Python 2 (essenzialmente costringendolo a usarlo long_powcome 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 intto longmentre 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_CONSTcarica il float 256.0anziché 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.