为什么Python 3的Tkinter画布控件运行缓慢?
我刚开始用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
模块也占了“问题”的十分之一 :)