Warum ist x**4.0
schneller als x**4
in Python 3 * ?
Python 3- int
Objekte sind vollwertige Objekte, die eine beliebige Größe unterstützen. Aus diesem Grund werden sie auf C-Ebene als solche behandelt (siehe, wie alle Variablen als PyLongObject *
Typ-In deklariert werden long_pow
). Dies macht ihre Potenzierung auch viel schwieriger und langwieriger, da Sie mit dem ob_digit
Array herumspielen müssen, das es verwendet, um seinen Wert darzustellen, um es auszuführen. ( Quelle für die Mutigen. - Siehe auch : Grundlegendes zu Speicherzuweisung für große Zahlen in Python für mehr auf PyLongObject
. S)
float
Im Gegensatz dazu können Python- Objekte double
(mithilfe von PyFloat_AsDouble
) in einen C- Typ umgewandelt werden, und Operationen können mit diesen nativen Typen ausgeführt werden . Das ist großartig , weil nach dem für relevante Ränder Fälle überprüft, es Python ermöglicht die Plattformenpow
( C ist pow
, das ist ) , um die tatsächliche Potenzierung zu handhaben :
/* 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);
wo iv
und iw
sind unsere originalen PyFloatObject
s als C double
s.
Für das, was es wert ist: Python ist 2.7.13
für mich ein Faktor 2~3
schneller und zeigt das umgekehrte Verhalten.
Die vorherige Tatsache erklärt auch die Diskrepanz zwischen Python 2 und 3, daher dachte ich, ich würde diesen Kommentar auch ansprechen, weil er interessant ist.
In Python 2 verwenden Sie das alte int
Objekt, das sich vom int
Objekt in Python 3 unterscheidet (alle int
Objekte in 3.x sind vom PyLongObject
Typ). In Python 2 gibt es eine Unterscheidung, die vom Wert des Objekts abhängt (oder, wenn Sie das Suffix verwenden L/l
):
# Python 2
type(30) # <type 'int'>
type(30L) # <type 'long'>
Die <type 'int'>
Sie hier sehen , macht das gleiche float
s tun , wird es sicher in eine C umgewandelt , long
wenn Potenzierung auf sie durchgeführt wird (die int_pow
auch Hinweise der Compiler ‚em in einem Register , wenn es so tun, so dass könnte einen Unterschied machen) ::
static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */
Dies ermöglicht einen guten Geschwindigkeitsgewinn.
Um zu sehen, wie träge <type 'long'>
s im Vergleich zu <type 'int'>
s sind, verschwindet der Geschwindigkeitsgewinn , wenn Sie den x
Namen in long
Python 2 in einen Aufruf eingeschlossen haben (was im Wesentlichen die Verwendung long_pow
wie in Python 3 erzwingt ):
# <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
Beachten Sie, dass, obwohl das eine Snippet das int
zu transformiert , long
während das andere nicht (wie von @pydsinger hervorgehoben), diese Besetzung nicht die treibende Kraft hinter der Verlangsamung ist. Die Umsetzung von long_pow
ist. (Zeit die Aussagen nur mit long(x)
zu sehen).
[...] es passiert nicht außerhalb der Schleife. [...] Irgendeine Idee dazu?
Dies ist CPythons Gucklochoptimierer, der die Konstanten für Sie faltet. In beiden Fällen erhalten Sie genau die gleichen Timings, da keine tatsächliche Berechnung zum Ermitteln des Ergebnisses der Potenzierung erfolgt, sondern nur das Laden von Werten:
dis.dis(compile('4 ** 4', '', 'exec'))
1 0 LOAD_CONST 2 (256)
3 POP_TOP
4 LOAD_CONST 1 (None)
7 RETURN_VALUE
Es wird ein identischer Bytecode generiert, '4 ** 4.'
mit dem einzigen Unterschied, dass LOAD_CONST
der Float 256.0
anstelle des int geladen wird 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
Die Zeiten sind also identisch.
* Alle oben genannten Punkte gelten ausschließlich für CPython, die Referenzimplementierung von Python. Andere Implementierungen funktionieren möglicherweise anders.