2015-06-12 13 views
7

Piszę grę kółko i krzyżyk i używając Enuma do reprezentowania trzech wyników - lose, draw i win. Pomyślałem, że będzie to lepszy styl niż użycie ciągów ("lose", "win", "draw") do wskazania tych wartości. Ale użycie wyrażeń spowodowało, że osiągnąłem znaczący sukces.Jak używać wyrażeń w języku Python 3.4 bez znaczącego spowolnienia?

Oto minimalny przykład, w którym po prostu odwołuję się do Result.lose lub literalnego ciągu znaków lose.

import enum 
import timeit 
class Result(enum.Enum): 
    lose = -1 
    draw = 0 
    win = 1 

>>> timeit.timeit('Result.lose', 'from __main__ import Result') 
1.705788521998329 
>>> timeit.timeit('"lose"', 'from __main__ import Result') 
0.024598151998361573 

Jest to znacznie wolniej niż po prostu odwołanie do zmiennej globalnej.

k = 12 

>>> timeit.timeit('k', 'from __main__ import k') 
0.02403248500195332 

Moje pytania są następujące:

  • wiem, że globalne wyszukiwań są znacznie wolniejsze niż lokalnych wyszukiwań w Pythonie. Ale dlaczego wyliczenia enum są jeszcze gorsze?
  • Jak efektywnie używać wyliczeń bez obniżania wydajności? Przegląd Enuma okazał się całkowicie dominujący w czasie wykonywania mojego programu "kółko i krzyżyk". Możemy zapisać lokalne kopie enum w każdej funkcji lub zawinąć wszystko w klasę, ale oba te wydają się niezręczne.
+0

Myślę, że prawdopodobnie pobieranie atrybutów jest powolne. Jeśli zrobisz coś takiego jak "lose = Result.lose", a następnie przetestujesz z 'lose', czy to lokalnym, czy globalnym, myślę, że zobaczysz wymierne przyspieszenie. – Shashank

+0

Dzięki, że działa całkiem dobrze. Czy wiesz, dlaczego wyszukiwanie atrybutów jest o wiele wolniejsze niż wyszukiwanie globalne? Wiem, że lokale są przechowywane w macierzy o stałej długości, podczas gdy globale są w dyktacie, ale co z atrybutami? –

+0

Nie wiem, przepraszam. I nie mogłem powiedzieć nic z pewnością bez czytania źródła CPython. Gdybym miał zgadywać, powiedziałbym, że obiekty są implementowane z asocjacyjnymi tablicami lub mapami lub czymkolwiek pod maską (tylko możliwość, nie należy traktować ich jako faktów), więc może być koszt dla algorytmu mieszania używanego na nazwach atrybutów które są jak klucze strunowe do tablicy mieszającej, ale to wszystko spekulacja. W każdym razie teraz wiesz, jak zminimalizować go w przypadku powtarzających się wyszukiwań. Lokalizacja ftw. – Shashank

Odpowiedz

10

Mierzysz taktowanie pętli taktowania. Łańcuch znaków na własną rękę jest ignorowane całkowicie:

>>> import dis 
>>> def f(): "lose" 
... 
>>> dis.dis(f) 
    1   0 LOAD_CONST    1 (None) 
       3 RETURN_VALUE   

To funkcja, która nie robi nic wcale. Pętla taktowania zajmuje 0.024598151998361573 sekund, aby uruchomić milion razy.

W tym przypadku, łańcuch faktycznie stał się docstring funkcji f:

>>> f.__doc__ 
'lose' 

ale CPython ogólnie pominie strunowe literały w kodzie, jeśli nie przypisane lub inaczej część wyrażenia:

>>> def f(): 
...  1 + 1 
...  "win" 
... 
>>> dis.dis(f) 
    2   0 LOAD_CONST    2 (2) 
       3 POP_TOP    

    3   4 LOAD_CONST    0 (None) 
       7 RETURN_VALUE   

Tutaj 1 + 1 jako złożony do stałej (2), a literał łańcuchowy ponownie zniknął.

W związku z tym nie można tego porównać do wyszukiwania atrybutu obiektu enum. Tak, wyszukiwanie atrybutów trwa cyklicznie. Ale także szuka innej zmiennej.

>>> import timeit 
>>> import enum 
>>> class Result(enum.Enum): 
...  lose = -1 
...  draw = 0 
...  win = 1 
... 
>>> timeit.timeit('outcome = Result.lose', 'from __main__ import Result') 
1.2259576459764503 
>>> timeit.timeit('outcome = lose', 'from __main__ import Result; lose = Result.lose') 
0.024848614004440606 

W timeit testy wszystkie zmienne są miejscowi, więc zarówno Result i lose są lokalne wyszukiwań: jeśli naprawdę martwisz się o wydajność, zawsze cache odnośnika atrybut może.

enum wyszukiwań atrybutów zrobić zająć trochę więcej czasu niż „zwykłych” wyszukiwań atrybut:

>>> class Foo: bar = 'baz' 
... 
>>> timeit.timeit('outcome = Foo.bar', 'from __main__ import Foo') 
0.04182224802207202 

To dlatego, że enum metaklasa zawiera specialised __getattr__ hook która jest wywoływana za każdym razem, gdy spojrzeć atrybut; atrybuty klasy enum są wyszukiwane w specjalistycznym słowniku, a nie w klasie __dict__. Zarówno wykonanie tej metody hak oraz dodatkowe wyszukiwanie atrybutu (aby uzyskać dostęp do mapy) podjęcia dodatkowego czasu:

>>> timeit.timeit('outcome = Result._member_map_["lose"]', 'from __main__ import Result') 
0.25198313599685207 
>>> timeit.timeit('outcome = map["lose"]', 'from __main__ import Result; map = Result._member_map_') 
0.14024519600206986 

W grze Tic-Tac-Toe nie martwić się o to, co zwykle sprowadza się do nieznacznych różnic przejściowych . Nie wtedy, gdy ludzki odtwarzacz jest wolniejszy o rząd wielkości niż twój komputer. Ten ludzki gracz nie zauważy różnicy między 1,2 mikrosekundy lub 0,024 mikrosekundy.

+0

Ah, ok, więc pętla dominuje, gdy nie używa się enum. Ale co stanowi różnicę między odwoływaniem się do zmiennej globalnej "k" i odwoływaniem się do 'Result.lose'? –

+0

@EliRose: wyszukiwanie ma koszt, tak. Więc jeśli to naprawdę ma znaczenie (np. W krytycznej części kodu, w pętli), buforuj wyszukiwanie w lokalnym. –

+0

Jestem bardziej zainteresowany wewnętrznymi obiektami niż w mojej grze w kółko i krzyżyk, w której istnieje wiele sposobów rozwiązania tego problemu. Dlaczego wyszukiwanie atrybutów jest o wiele wolniejsze niż wyszukiwanie globalne? –

Powiązane problemy