2011-02-05 4 views
12

sto replica di un piccolo pezzo di Sugarscape agente modello di simulazione in Python 3. Ho trovato le prestazioni del mio codice è ~ 3 volte più lento di quello di NetLogo. Probabilmente è il problema con il mio codice, o può essere il limite intrinseco di Python?simulazione basata su agenti: problema di prestazioni: Python vs NetLogo & Repast

Ovviamente, questo è solo un frammento di codice, ma è lì che Python spende due terzi del runtime. Spero che se ho scritto qualcosa di veramente inefficiente potrebbe presentarsi in questo frammento:

UP = (0, -1) 
RIGHT = (1, 0) 
DOWN = (0, 1) 
LEFT = (-1, 0) 
all_directions = [UP, DOWN, RIGHT, LEFT] 
# point is just a tuple (x, y) 
def look_around(self): 
    max_sugar_point = self.point 
    max_sugar = self.world.sugar_map[self.point].level 
    min_range = 0 

    random.shuffle(self.all_directions) 
    for r in range(1, self.vision+1): 
     for d in self.all_directions: 
      p = ((self.point[0] + r * d[0]) % self.world.surface.length, 
       (self.point[1] + r * d[1]) % self.world.surface.height) 
      if self.world.occupied(p): # checks if p is in a lookup table (dict) 
       continue 
      if self.world.sugar_map[p].level > max_sugar: 
       max_sugar = self.world.sugar_map[p].level 
       max_sugar_point = p 
    if max_sugar_point is not self.point: 
     self.move(max_sugar_point) 

o meno equivalente code in NetLogo (questo frammento fa un po 'di più rispetto alla funzione Python sopra):

; -- The SugarScape growth and motion procedures. -- 
to M ; Motion rule (page 25) 
    locals [ps p v d] 
    set ps (patches at-points neighborhood) with [count turtles-here = 0] 
    if (count ps > 0) [ 
     set v psugar-of max-one-of ps [psugar]    ; v is max sugar w/in vision 
     set ps ps with [psugar = v]       ; ps is legal sites w/ v sugar 
     set d distance min-one-of ps [distance myself]  ; d is min dist from me to ps agents 
     set p random-one-of ps with [distance myself = d] ; p is one of the min dist patches 
     if (psugar >= v and includeMyPatch?) [set p patch-here] 
     setxy pxcor-of p pycor-of p       ; jump to p 
     set sugar sugar + psugar-of p      ; consume its sugar 
     ask p [setpsugar 0]         ; .. setting its sugar to 0 
    ] 
    set sugar sugar - metabolism ; eat sugar (metabolism) 
    set age age + 1 
end 

Sul mio computer , il codice Python impiega 15.5 secondi per eseguire 1000 passi; sullo stesso laptop, la simulazione NetLogo in esecuzione in Java all'interno del browser termina 1000 passaggi in meno di 6 secondi.

MODIFICA: è stato appena controllato Repast, utilizzando l'implementazione Java. Ed è anche lo stesso di NetLogo a 5.4 secondi. Recent comparisons tra Java e Python non suggeriscono alcun vantaggio per Java, quindi suppongo che sia colpa solo del mio codice?

MODIFICA: Capisco che lo MASON si suppone sia ancora più veloce di Repast e che alla fine esegua ancora Java.

risposta

11

Questo probabilmente non darà incrementi nella velocità drammatici, ma si deve essere consapevoli del fatto che le variabili locali sono un po 'più veloce in Python rispetto all'accesso globali o attributi. Così si potrebbe provare l'assegnazione di alcuni valori che vengono utilizzati nel ciclo interno in locali, in questo modo:

def look_around(self): 
    max_sugar_point = self.point 
    max_sugar = self.world.sugar_map[self.point].level 
    min_range = 0 

    selfx = self.point[0] 
    selfy = self.point[1] 
    wlength = self.world.surface.length 
    wheight = self.world.surface.height 
    occupied = self.world.occupied 
    sugar_map = self.world.sugar_map 
    all_directions = self.all_directions 

    random.shuffle(all_directions) 
    for r in range(1, self.vision+1): 
     for dx,dy in all_directions: 
      p = ((selfx + r * dx) % wlength, 
       (selfy + r * dy) % wheight) 
      if occupied(p): # checks if p is in a lookup table (dict) 
       continue 
      if sugar_map[p].level > max_sugar: 
       max_sugar = sugar_map[p].level 
       max_sugar_point = p 
    if max_sugar_point is not self.point: 
     self.move(max_sugar_point) 

funzione chiamate in Python hanno anche una relativamente alta in testa (rispetto a Java), in modo da poter cercare di ottimizzare ulteriormente sostituendo la funzione occupied con una ricerca di dizionario diretta.

Si dovrebbe anche dare un'occhiata a psyco. È un compilatore just-in-time per Python che in alcuni casi può migliorare sensibilmente la velocità. Tuttavia, non supporta ancora Python 3.x, quindi è necessario utilizzare una versione precedente di Python.

+1

Nizza. Il tempo di esecuzione è diminuito da 15,5 a 11,4 s (~ 26%). Perché diavolo Python non avrebbe potuto ottimizzare quella roba, almeno quando specificassi l'opzione -O all'interprete ??? – max

+1

E sono sceso ulteriormente a 10,4 secondi quando mi sono liberato della funzione 'Occupata'. – max

+1

@ max: grazie per aver scritto con gli acceleratori che hai riscontrato. – Curious2learn

4

ho intenzione di indovinare che il modo in cui è implementato in neighborhood NetLogo è diverso dal doppio anello che avete. In particolare, penso che pre-calcolano un vettore quartiere come

n = [ [0,1],[0,-1],[1,0],[-1,0]....] 

(si avrebbe bisogno di una diversa per la visione = 1,2, ...) e poi utilizzare un solo ciclo su n invece di un ciclo nidificato come stai facendo Questo elimina la necessità delle moltiplicazioni.

non credo che questo ti porterà 3X aumento di velocità.

+0

Abbiamo bisogno di un punto casuale tra i punti zucchero più alti. Nel mio approccio, è fatto da un singolo 'shuffle' sulle direzioni prima del ciclo. In NetLogo, è fatto tenendo traccia di tutti i migliori punti e poi sceglierne uno a caso dalla lista (l'ho provato in Python ma era leggermente più lento). Nel tuo approccio, dovrei provare a tenere traccia di più punti migliori, oa "mischiare" ciascuno dei vettori di quartiere (n 'shuffle's anziché uno). Ci proverò. – max

+0

Inoltre, poiché ho bisogno di controllare il wrapping nel toro, avrei bisogno di precalcolare il quartiere per ogni punto o continuare a usare l'operazione '%'. Il primo è un po 'più veloce, il secondo è molto più lento. – max

+0

Un modo più veloce per ottenere il comportamento del mondo wrap-around da netLogo sarebbe quello di rendere il tuo mondo modellato all'interno di una matrice numpy con indici extra ad entrambe le estremità nelle direzioni xey. Se la distanza adiacente era 1, gli indici dell'array andavano da 0 a width + 1 e da 0 a height + 1. I calcoli devono essere eseguiti solo all'interno del sottoinsieme degli indici che rappresentano il mondo, e quindi i valori delle patch per gli indici extra alle estremità di ciascuna dimensione possono essere copiati dal mondo all'altra estremità. Questo evita il calcolo della% per ogni equivalente python della patch netLogo nell'intero mondo. –

3

Questa è una vecchia questione, ma vi suggerisco di guardare in utilizzando NumPy per velocizzare le operazioni. I luoghi in cui usi dicts e liste che sono organizzate in modo logico (1-, 2, 3 o N-dimensional) oggetti dati omogenei (tutti gli interi, o tutti i float, ecc.) Avranno meno overhead quando rappresentati e accessibili come Numpy array.

http://numpy.org