Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
647 views
in Technique[技术] by (71.8m points)

python - Why does my Game of Life simulation slow down to a crawl within seconds? Matplotlib to blame?

I'm learning OOP in python and so for a bit of fun I bashed out a GameOfLife simulator this morning. When it starts up it runs at about 20 cycles per second (due to the plt.pause(0.05) I added), but within seconds it slows down to ~ 2 cycles per second.

I can't imagine it's the algorithm itself, and I can't see any obvious sources for memory leaks.

Is matplotlib failing to dump the old plots, is that the issue, causing thousands of overlayed images building up? I tried adding del im but it didn't help...

This isn't important, really, but I feel I could learn something from the answer.

PS if you think my implementation is poor then do let me know, I'm keen to learn!

import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import convolve
import time

class GoL():

    KERNEL = np.array([[1, 1, 1],
                       [1, 0, 1],
                       [1, 1, 1]])

    def __init__(self, width, height, p=0.3):
        self.width = width
        self.height = height
        self.matrix = np.random.choice(a=[1, 0], size=(width, height), p=[p, 1 - p])

    def play(self):
        self.plot()
        while True:
            self.cycle()

    def cycle(self):
        c = self.eval()
        for x in range(1,self.width-1):
            for y in range(1,self.height-1):
                c_val = c[x,y]
                if c_val == 3: self.matrix[x,y] = 1 #Alive regardless of whether cell alive or dead
                elif c_val < 2: self.matrix[x,y] = 0 #Dead regardless of whether cell alive or dead
                elif c_val > 3: self.matrix[x,y] = 0 #Dead regardless of whether cell alive or dead
                elif self.matrix[x,y] == 1 and c_val == 2: self.matrix[x,y] = 1 #If a living cell with 2 neighours, live
                else: self.matrix[x,y] = 0 #Only other option is dead with 2 neighbours; die
        self.plot()

    def eval(self):
        c = convolve(self.matrix, GoL.KERNEL, mode='constant')
        return c

    def plot(self):
        im = plt.imshow(self.matrix)
        plt.pause(0.05)
        del im #Added to see if speeds things up; it does not

And to run:

gol = GoL(width=100,height=100)
gol.play()
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You will see that the images of previous calls are still present by printing the number of images inside the plot function,

print ( len(plt.gca().images) )

In your case this number will increase steadily, even though you delete the image, because it is still part of the axes and hence get redrawn every iteration.

It would be much better to draw the image once, and then only update its data.

class GoL():
    # ...

    def __init__(self, width, height, p=0.3):
        self.width = width
        self.height = height
        self.matrix = np.random.choice(a=[1, 0], size=(width, height), p=[p, 1 - p])
        self.im = plt.imshow(self.matrix)

    #  ....

    def plot(self):
        self.im.set_data(self.matrix)
        plt.pause(0.05)
        print len(plt.gca().images)

This will result in an animation at constant speed.


Concerning the question on where to improve the code, there are two things:
  1. use matplotlib.animation.FuncAnimation, as this is much more stable and lets you savely terminate the animation.
  2. use conditions on numpy arrays instead of looping over all pixels of the matrix.

Full code:

import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import convolve
import matplotlib.animation as animation

class GoL():

    KERNEL = np.array([[1, 1, 1],
                       [1, 0, 1],
                       [1, 1, 1]])

    def __init__(self, width, height, p=0.3):
        self.width = width
        self.height = height
        self.matrix = np.random.choice(a=[1, 0], size=(width, height), p=[p, 1 - p])
        self.im = plt.imshow(self.matrix)

    def play(self):
        self.plot()
        self.ani= animation.FuncAnimation(plt.gcf(), self.cycle, repeat=True, interval=50 )
        plt.show()

    def cycle(self, n):
        c = self.eval()
        new = np.zeros_like(self.matrix)
        new[c == 3] = 1
        new[c < 2] = 0
        new[c > 3] = 0
        new[(self.matrix == 1) & (c == 2)] = 1
        self.matrix = new
        self.plot()

    def eval(self):
        return convolve(self.matrix, self.KERNEL, mode='constant')

    def plot(self):
        self.im.set_data(self.matrix)

gol = GoL(width=100,height=100)
gol.play()

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...