为什么Python 3的Tkinter画布控件运行缓慢?

0 投票
2 回答
3774 浏览
提问于 2025-04-17 14:49

我刚开始用Python(3.2)中的Tkinter模块,所以决定用这个模块重写我以前的程序(我之前用的是curses模块)。这个程序是一个生命游戏模拟器。

我实现的算法在没有用户界面的情况下运行得非常快。这是我的程序(这是一个快速实验,我从来没有使用过画布控件):

#!/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()

我没有报告生命游戏的模块,但问题不在那个模块上。问题是程序运行得很慢,我觉得我对画布的使用还不够好。

编辑:这是生命游戏的模块,但我觉得这不是问题所在……

#!/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

2 个回答

1

我猜是因为你每次重绘棋盘时都在创建一个新的900个对象的网格。大家都知道,当你创建成千上万的对象时,画布会出现性能问题。因为你每次都在绘制900个对象,这样很快就会累积很多对象。

我的建议是,先把30x30的方格网绘制一次,然后在每次迭代时只需要改变每个方格的颜色。这样就能让你快速循环生成了。

3

这是你个人资料的一部分:

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}

我们来看看这里对你来说有什么有趣的内容:

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)

你大部分时间都在 draw 这个地方,这个方法是在 Application 类的 play 方法里。

这些代码:

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)

说明你其实是在花时间创建你的矩形。

所以如果你想提高性能,最好不要每次都重新创建东西。只需要更新它们就行了!顺便提一下,如果你用矩阵代替双重循环,你会发现对 draw 的调用会少很多。因为 draw 的速度很慢(总共耗时6.787秒),而且你在这些循环里也浪费了将近1.5秒。

另外,gol.py 中的 evolve 也可以通过去掉这些双重循环来大幅提升性能。我觉得你在算法上也可以做一些改进。

编辑

哦,还有 gol 模块也占了“问题”的十分之一 :)

撰写回答