2011-11-09 22 views
79

jednoczesnej optymalizacji mój kod zdałem sobie sprawę, co następuje:Python: dlaczego są * i ** szybsze niż/i sqrt()?

>>> from timeit import Timer as T 
>>> T(lambda : 1234567890/4.0).repeat() 
[0.22256922721862793, 0.20560789108276367, 0.20530295372009277] 
>>> from __future__ import division 
>>> T(lambda : 1234567890/4).repeat() 
[0.14969301223754883, 0.14155197143554688, 0.14141488075256348] 
>>> T(lambda : 1234567890 * 0.25).repeat() 
[0.13619112968444824, 0.1281130313873291, 0.12830305099487305] 

a ponadto:

>>> from math import sqrt 
>>> T(lambda : sqrt(1234567890)).repeat() 
[0.2597470283508301, 0.2498021125793457, 0.24994492530822754] 
>>> T(lambda : 1234567890 ** 0.5).repeat() 
[0.15409398078918457, 0.14059877395629883, 0.14049601554870605] 

Zakładam, że ma do czynienia ze sposobem, pyton jest realizowany w C, ale zastanawiam się, czy ktoś będzie dbać wyjaśnić, dlaczego tak się dzieje?

+0

Odpowiedź, którą zaakceptowałeś na swoje pytanie (które, jak zakładam, odpowiada na twoje prawdziwe pytanie), nie ma wiele wspólnego z tytułem pytania. Czy możesz go edytować, aby mieć coś wspólnego ze stałym składaniem? –

+1

@ZanLynx - Hi. Czy mógłbyś wyjaśnić? Uważam, że tytuł pytania dokładnie wyraża to, co chciałem wiedzieć (dlaczego X jest szybszy od Y), a odpowiedź, którą wybrałem, dokładnie to brzmi ... Wydaje mi się idealnie pasować do mnie ... ale może coś przeoczyłem? – mac

+8

Funkcje mnożenia i zasilania są zawsze szybsze niż funkcje dzielenia i sqrt() ze względu na ich charakter. Podział i operacje root'a zazwyczaj muszą korzystać z szeregu dokładniejszych i dokładniejszych przybliżeń i nie mogą przejść bezpośrednio do właściwej odpowiedzi, jak mnożenie. –

Odpowiedz

112

(Nieco nieoczekiwanym) powodem twoich wyników jest to, że Python wydaje się składać wyrażenia stałe, w tym mnożenie zmiennoprzecinkowe i potęgowanie, ale nie dzielenie. math.sqrt() to zupełnie inna bestia, ponieważ nie ma dla niej kodu bajtowego i wymaga wywołania funkcji.

na Python 2.6.5, następujący kod:

x1 = 1234567890.0/4.0 
x2 = 1234567890.0 * 0.25 
x3 = 1234567890.0 ** 0.5 
x4 = math.sqrt(1234567890.0) 

kompiluje się do następujących bytecodes:

# x1 = 1234567890.0/4.0 
    4   0 LOAD_CONST    1 (1234567890.0) 
       3 LOAD_CONST    2 (4.0) 
       6 BINARY_DIVIDE  
       7 STORE_FAST    0 (x1) 

    # x2 = 1234567890.0 * 0.25 
    5   10 LOAD_CONST    5 (308641972.5) 
      13 STORE_FAST    1 (x2) 

    # x3 = 1234567890.0 ** 0.5 
    6   16 LOAD_CONST    6 (35136.418286444619) 
      19 STORE_FAST    2 (x3) 

    # x4 = math.sqrt(1234567890.0) 
    7   22 LOAD_GLOBAL    0 (math) 
      25 LOAD_ATTR    1 (sqrt) 
      28 LOAD_CONST    1 (1234567890.0) 
      31 CALL_FUNCTION   1 
      34 STORE_FAST    3 (x4) 

Jak widać, mnożenie i potęgowanie brać w ogóle czasu, ponieważ” zrobić ponownie, gdy kod zostanie skompilowany. Podział trwa dłużej, ponieważ dzieje się to w czasie wykonywania. Pierwiastek kwadratowy jest nie tylko najbardziej kosztowną obliczeniowo operacją czwórki, ale także powoduje różne koszty ogólne, których inni nie robią (wyszukiwanie atrybutów, wywoływanie funkcji itp.).

Jeśli wyeliminować efekt ciągłego zwijania, tam trochę oddzielić mnożenie i dzielenie:

In [16]: x = 1234567890.0 

In [17]: %timeit x/4.0 
10000000 loops, best of 3: 87.8 ns per loop 

In [18]: %timeit x * 0.25 
10000000 loops, best of 3: 91.6 ns per loop 

math.sqrt(x) jest faktycznie nieco szybciej niż x ** 0.5, przypuszczalnie dlatego, że jest szczególnym przypadkiem tego ostatniego, a zatem być bardziej efektywnie zrobić, pomimo ogólnych:

In [19]: %timeit x ** 0.5 
1000000 loops, best of 3: 211 ns per loop 

In [20]: %timeit math.sqrt(x) 
10000000 loops, best of 3: 181 ns per loop 

edycji 2011-11-16: Stała składany ekspresja jest wykonywana przez PE Pythona Optymalizator ephole. Kod źródłowy (peephole.c) zawiera następujące komentarz, który wyjaśnia dlaczego stały podział nie jest złożona:

case BINARY_DIVIDE: 
     /* Cannot fold this operation statically since 
      the result can depend on the run-time presence 
      of the -Qnew flag */ 
     return 0; 

-Qnew flaga umożliwia podział „prawdziwą” zdefiniowanego w PEP 238.

+2

Być może jest to "ochrona" przed dzieleniem przez zero? – hugomg

+2

@missingno: Nie jest dla mnie jasne, dlaczego taka "ochrona" byłaby konieczna, ponieważ oba argumenty są znane w czasie kompilacji, podobnie jak wynik (który jest jednym z: + inf, -inf, NaN). – NPE

+1

może test 'from __future__ import division' używa podobnej metody. – Simon

Powiązane problemy