Question

I've just started to use the Tkinter module with Python(3.2), so I decided to rewrite my old program(I used curses module) with this module. The program is a Game of Life simulator. The algorithm I've implemented works so quickly without the user interface. This is my program(it is a fast experiment, I never used the canvas widget):

#!/usr/bin/python3

import gol
import Tkinter as tk

class Application(tk.Frame):
    def __init__(self):
        self.root = tk.Tk() 
        self.root.wm_title('Canvas Experiments')
        tk.Frame.__init__(self, self.root)

        self.draw_widgets()

        self.world = gol.World(30, 30)
        self.world.cells[25][26] = True
        self.world.cells[26][26] = True
        self.world.cells[27][26] = True
        self.world.cells[25][27] = True
        self.world.cells[26][28] = True

    def draw_widgets(self):
        self.canvas = tk.Canvas(
            width = 300,
            height = 300,
            bg = '#FFF')
        self.canvas.grid(row = 0)

        self.b_next = tk.Button(
            text = 'Next',
            command = self.play)
        self.b_next.grid(row = 1)

        self.grid()

    def play(self):    
        def draw(x, y, alive): 
            if alive:   
                self.canvas.create_rectangle(x*10, y*10, x*10+9, y*10+9, fill='#F00')
            else:       
                self.canvas.create_rectangle(x*10, y*10, x*10+9, y*10+9, fill='#FFF')

        for y in range(self.world.width):
            for x in range(self.world.height):
                draw(x, y, self.world.cells[x][y])      

        self.world.evolve()

app = Application()
app.mainloop()

I didn't report the gol but the problems are not in that module. The problem is that the program is very slow, I think that I'm not able to use the canvas very good.

EDIT: Here is the gol module, but I don't think this is the problem...

#!/usr/bin/python3

class World:
    def __init__(self, width, height):
        self.width, self.height = width, height
        self.cells = [[False for row in range(self.height)] for column in range(self.width)]

    def neighbours(self, x, y): 
        counter = 0
        for i in range(-1, 2):
            for j in range(-1, 2):
                if ((0 <= x + i < self.width) and (0 <= y + j < self.height) and not (i == 0 and j == 0)): 
                    if self.cells[x + i][y + j]:
                        counter += 1            
        return counter        

    def evolve(self):
        cells_tmp = [[False for row in range(self.height)] for column in range(self.width)]
        for x in range(self.width):
            for y in range(self.height):
                if self.cells[x][y]:
                    if self.neighbours(x, y) == 2 or self.neighbours(x, y) == 3:
                        cells_tmp[x][y] = True  
                else:           
                    if self.neighbours(x, y) == 3:
                        cells_tmp[x][y] = True  
        self.cells = cells_tmp
Was it helpful?

Solution

Here is part of your profile:

ncalls  tottime  percall  cumtime  percall filename:lineno(function) 
125112    1.499    0.000    1.499    0.000 {method 'call' of 'tkapp' objects} 
125100    1.118    0.000    6.006    0.000 /usr/lib/python3.2/tkinter/__init__.py:2190(_create)
125109    0.942    0.000    1.749    0.000 /usr/lib/python3.2/tkinter/__init__.py:69(_cnfmerge)
125106    0.906    0.000    3.065    0.000 /usr/lib/python3.2/tkinter/__init__.py:1059(_options)
125599    0.851    0.000    0.851    0.000 main.py:10(neighbours)
500433    0.688    0.000    0.688    0.000 {built-in method isinstance}
125100    0.460    0.000    6.787    0.000 main.py:64(draw)
250210    0.341    0.000    0.341    0.000 {method 'update' of 'dict' objects}
125100    0.321    0.000    6.327    0.000 /usr/lib/python3.2/tkinter/__init__.py:2219(create_rectangle)
250205    0.319    0.000    0.319    0.000 {built-in method _flatten}
   139    0.255    0.002    8.093    0.058 main.py:63(play)
   139    0.181    0.001    1.051    0.008 main.py:19(evolve)
125109    0.134    0.000    0.134    0.000 {method 'items' of 'dict' objects}
125108    0.107    0.000    0.107    0.000 {built-in method callable}
     1    0.056    0.056    0.056    0.056 {built-in method create}

Let's extract what is interesting for you here:

cumtime   filename:lineno(function) 
0.851    main.py:10(neighbours)
6.787    main.py:64(draw)
8.093    main.py:63(play)
1.051    main.py:19(evolve)

You spend most of your time in draw, contained in the method play of the class Application.

The lines:

ncalls  tottime  percall  cumtime  percall filename:lineno(function) 
125100    1.118    0.000    6.006    0.000 /usr/lib/python3.2/tkinter/__init__.py:2190(_create)
125106    0.906    0.000    3.065    0.000 /usr/lib/python3.2/tkinter/__init__.py:1059(_options)

show that your are actually spending your time to create your rectangles.

So if you want to get better performances, just stop instanciating stuff. Just update them! By the way, you'll get far fewer calls to draw if you use matrices instead of double loops. draw is slow (6.787 cum. seconds), but remember that you also waste almost 1.5 seconds in these loops.

By the way, evolve in gol.py can also be greatly improved by removing these double loops. I think you can make some improvement in the algorithm too.

EDIT:

Oh, and the gol module contributes to on tenth of the "problem" :)

OTHER TIPS

My guess is, it's because you're creating a new grid of 900 objects each time you redraw the board. The canvas is known to have performance issues when you create tens of thousands of objects. Since you're drawing 900 objects on each iteration, that will quickly add up to a lot of objects.

My advice is to refactor your code to draw the grid of 30x30 squares once, then on each iteration you need to only change the color of each grid element. That should be enough to let you cycle through generations very quickly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top