Simulation basée sur l'agent: Performance Issue: Python vs Netlogo & Repast
-
29-10-2019 - |
Question
Je reproduisant un petit morceau de modèle de simulation d'agent de paysage de sucre dans Python 3. J'ai trouvé que les performances de mon code sont ~ 3 fois plus lentes que celles de Netlogo. Est-ce probablement le problème avec mon code, ou peut-il être la limitation inhérente de Python?
De toute évidence, ce n'est qu'un fragment du code, mais c'est là que Python passe les deux tiers de l'exécution. J'espère que si j'ai écrit quelque chose de vraiment inefficace, cela pourrait apparaître dans ce fragment:
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)
Approximativement égal code dans netlogo (Ce fragment fait un peu plus que la fonction Python ci-dessus):
; -- 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
Sur mon ordinateur, le code Python prend 15,5 secondes pour exécuter 1000 étapes; Sur le même ordinateur portable, la simulation Netlogo fonctionnant en Java à l'intérieur du navigateur termine 1000 étapes en moins de 6 secondes.
EDIT: Je viens de vérifier la répange, en utilisant la mise en œuvre de Java. Et c'est aussi à peu près la même que Netlogo à 5,4 secondes. Comparaisons récentes Entre Java et Python ne suggèrent aucun avantage à Java, donc je suppose que c'est juste mon code qui est à blâmer?
Edit: je comprends LE MAÇON est censé être encore plus rapide que le repast, et pourtant il exécute toujours Java à la fin.
La solution
Cela ne donnera probablement pas de vitesses spectaculaires, mais vous devez savoir que les variables locales sont un peu plus rapides en Python par rapport à l'accès aux globaux ou aux attributs. Vous pouvez donc essayer d'attribuer certaines valeurs utilisées dans la boucle intérieure dans les habitants, comme ceci:
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)
Les appels de fonction dans Python ont également une surcharge relativement élevée (par rapport à Java), vous pouvez donc essayer d'optimiser davantage en remplaçant le occupied
Fonction avec une recherche directe de dictionnaire.
Vous devriez également jeter un œil à Psyco. C'est un compilateur juste à temps pour Python qui peut donner des améliorations de vitesse spectaculaires dans certains cas. Cependant, il ne prend pas encore en charge Python 3.x, vous devez donc utiliser une ancienne version de Python.
Autres conseils
Je vais deviner que la façon dont ça neighborhood
est implémenté dans Netlogo est différent de la boucle double que vous avez. Plus précisément, je pense qu'ils pré-calculent un vecteur de quartier comme
n = [ [0,1],[0,-1],[1,0],[-1,0]....]
(vous auriez besoin d'un autre pour la vision = 1,2, ...) et ensuite utiliser une seule boucle n
Au lieu d'une boucle imbriquée comme vous le faites. Cela élimine le besoin des multiplications.
Je ne pense pas que cela vous amènera 3X.
C'est une vieille question, mais je vous suggère de chercher à utiliser Numpy pour accélérer vos opérations. Les endroits où vous utilisez des dicts et des listes qui sont logiquement organisés (1, 2, 3, ou N-Dimensional Grid) Objet de données homogène (tous les entiers, ou tous les flotteurs, etc.) auront moins de frais généraux lorsqu'ils sont représentés et accessibles en tant que numpy tableaux.
Voici une comparaison relativement à jour de Netlogo et une version de Repast. Je ne supposerais pas nécessairement que le Repast est plus rapide. Netlogo semble contenir des algorithmes très intelligents qui peuvent compenser les coûts qu'il a.http://condor.depaul.edu/slytinen/abm/lytinen-railsback-emcsr_2012-02-17.pdf