Dlaczego jest x**4.0
szybszy niż x**4
w Pythonie 3 * ?
int
Obiekty Pythona 3 są pełnoprawnymi obiektami zaprojektowanymi do obsługi dowolnego rozmiaru; w związku z tym są traktowane jako takie na poziomie C (zobacz, jak wszystkie zmienne są deklarowane jako PyLongObject *
typ in long_pow
). To również sprawia, że ich potęgowanie jest znacznie trudniejsze i bardziej żmudne, ponieważ musisz bawić się ob_digit
tablicą, której używa do reprezentowania jej wartości, aby ją wykonać. ( Źródło dla odważnych. - Zobacz: Zrozumienie alokacji pamięci dla dużych liczb całkowitych w Pythonie, aby uzyskać więcej informacji na temat PyLongObject
s.)
float
Wręcz przeciwnie, obiekty Pythona można przekształcić do double
typu C (przy użyciu PyFloat_AsDouble
), a operacje można wykonywać przy użyciu tych typów natywnych . To jest wielki , ponieważ po sprawdzeniu odpowiednich krawędziowych przypadkach pozwala Pythona do korzystania z platformypow
( C użytkownika pow
, że jest ), aby obsłużyć rzeczywisty potęgowanie:
/* 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);
gdzie iv
i iw
są naszymi oryginałami PyFloatObject
jako C double
s.
Bo to jest warte: Python jest 2.7.13
dla mnie czynnikiem 2~3
szybszym i wykazuje odwrotne zachowanie.
Poprzedni fakt wyjaśnia również rozbieżność między Pythonem 2 i 3, więc pomyślałem, że odniosę się również do tego komentarza, ponieważ jest interesujący.
W Pythonie 2 używasz starego int
obiektu, który różni się od int
obiektu w Pythonie 3 (wszystkie int
obiekty w 3.x są PyLongObject
typu). W Pythonie 2 istnieje różnica, która zależy od wartości obiektu (lub, jeśli używasz przyrostka L/l
):
# Python 2
type(30) # <type 'int'>
type(30L) # <type 'long'>
To, <type 'int'>
co widzisz tutaj, robi to samo, co float
robi , jest bezpiecznie konwertowane na C, long
gdy wykonywane jest na nim potęgowanie ( int_pow
podpowiada również kompilatorowi, aby umieścić je w rejestrze, jeśli może to zrobić, więc może to zrobić różnicę) :
static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */
pozwala to na dobry przyrost prędkości.
Aby zobaczyć, jak powolne <type 'long'>
są s w porównaniu do <type 'int'>
s, jeśli umieścisz x
nazwę w long
wywołaniu w Pythonie 2 (zasadniczo zmuszając ją do użycia long_pow
jak w Pythonie 3), przyrost prędkości znika:
# <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
Należy wziąć pod uwagę, że choć jeden snippet przekształca się int
do long
podczas gdy inne nie (jak podkreślił @pydsinger), to obsada nie jest przyczynianie się siłą spowolnienia. Wdrożenie long_pow
is. (Zmierz czas tylko z wyrażeniami, long(x)
aby zobaczyć).
[…] nie dzieje się to poza pętlą. […] Masz o tym jakiś pomysł?
To jest optymalizator wizualizacji CPythona składający stałe za Ciebie. W obu przypadkach otrzymujesz te same dokładne czasy, ponieważ nie ma rzeczywistych obliczeń, które pozwolą znaleźć wynik potęgowania, tylko ładowanie wartości:
dis.dis(compile('4 ** 4', '', 'exec'))
1 0 LOAD_CONST 2 (256)
3 POP_TOP
4 LOAD_CONST 1 (None)
7 RETURN_VALUE
Generowany jest identyczny kod bajtowy, '4 ** 4.'
z tą różnicą, że LOAD_CONST
ładuje zmiennoprzecinkowy 256.0
zamiast 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
Więc czasy są identyczne.
* Wszystkie powyższe dotyczą wyłącznie CPythona, referencyjnej implementacji Pythona. Inne implementacje mogą działać inaczej.