2015-06-12 5 views
7

Stavo scrivendo un gioco tic-tac-toe e utilizzando un Enum per rappresentare i tre risultati - lose, draw e win. Ho pensato che sarebbe stato meglio usare le stringhe ("lose", "win", "draw") per indicare questi valori. Ma usare le enumerate mi ha dato un notevole successo nelle prestazioni.Come usare le enumerazioni di Python 3.4 senza un rallentamento significativo?

Ecco un esempio minimo, in cui faccio semplicemente riferimento a Result.lose o alla stringa letterale 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 

Questo è molto più lento del semplice riferimento a una variabile globale.

k = 12 

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

Le mie domande sono:

  • So che le ricerche globali sono molto più lenti di quanto le ricerche locali in Python. Ma perché le occhiate enum sono ancora peggio?
  • In che modo le enumerazioni possono essere utilizzate in modo efficace senza sacrificare le prestazioni? La ricerca di Enum si è rivelata completamente dominante sul runtime del mio programma tic-tac-toe. Potremmo salvare copie locali dell'enum in ogni funzione, o avvolgere tutto in una classe, ma entrambe sembrano imbarazzanti.
+0

Penso che sia probabilmente il recupero degli attributi che è lento. Se fai qualcosa come 'lose = Result.lose' e ​​poi prova contro' lose', sia esso locale o globale, penso che vedrai un aumento misurabile. – Shashank

+0

Grazie, funziona abbastanza bene. Sai perché la ricerca degli attributi è molto più lenta della ricerca globale? So che i locali sono archiviati in un array a lunghezza fissa mentre i globals sono in un ditt, ma qual è l'accordo con gli attributi? –

+0

Non lo so, mi dispiace. E non potrei dirti nulla con certezza senza leggere la fonte CPython. Se dovessi indovinare, direi che gli oggetti sono implementati con array associativi o mappe o qualsiasi altra cosa sotto il cofano (solo una possibilità, non da considerare come un fatto), quindi potrebbe esserci un costo per l'algoritmo di hash utilizzato sui nomi degli attributi che sono come chiavi di stringa per una tabella hash, ma questa è tutta la speculazione. In ogni caso, ora sai come ridurlo al minimo nel caso di ricerche ripetitive. Localizzazione ftw. – Shashank

risposta

10

Si sta cronometrando il ciclo di temporizzazione. Una stringa letterale da solo è ignorato del tutto:

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

Questa è una funzione che non fa nulla affatto. Quindi il ciclo temporale richiede 0.024598151998361573 secondi per essere eseguito 1 milione di volte.

In questo caso, la stringa effettivamente diventato docstring della funzione f:

>>> f.__doc__ 
'lose' 

ma CPython generalmente ometterà letterali stringa nel codice se non assegnati o altrimenti una parte di espressione:

>>> 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   

Qui lo 1 + 1 è piegato in una costante (2) e la stringa letterale è ancora una volta scomparsa.

Come tale, non è possibile confrontare questo per cercare un attributo su un oggetto enum. Sì, la ricerca di un attributo richiede cicli. Ma anche la ricerca di un'altra variabile. Se davvero siete preoccupati per le prestazioni, si può sempre di cache la ricerca attributo:

>>> 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 

In timeit test tutte le variabili sono locali, in modo che entrambi Result e lose sono le ricerche locali.

enum le ricerche degli attributi fanno prendere un po 'più tempo di 'regolare' le ricerche di attributo:

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

Questo perché il metaclasse enum include un specialised __getattr__ hook che viene chiamato ogni volta che si guarda un attributo; gli attributi di una classe enum vengono cercati in un dizionario specializzato piuttosto che nella classe __dict__. Sia l'esecuzione di quel metodo gancio e la ricerca attributo aggiuntivo (per accedere alla mappa) prendere ulteriore tempo:

>>> 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 

In un gioco di Tic-Tac-Toe non generalmente preoccuparsi di ciò che si riduce a differenze temporanee insignificanti . Non quando il giocatore umano è di ordine di grandezza più lento del computer. Quel giocatore umano non noterà la differenza tra 1,2 microsecondi o 0,024 microsecondi.

+0

Ah, okay, quindi il loop domina quando non si usa l'enum. Ma quale è la differenza tra fare riferimento alla variabile globale 'k' e fare riferimento a' Result.lose'? –

+0

@EliRose: la ricerca ha un costo, sì. Quindi, se ciò è realmente importante (ad esempio in una parte temporale del codice, in un ciclo), memorizzare la ricerca in locale. –

+0

Sono più interessato agli interni di quanto non sia nel mio gioco tic-tac-toe, in cui ci sono molti modi per risolvere questo problema. Perché la ricerca degli attributi è molto più lenta della ricerca globale? –