了解Tkinter Canvas的性能限制

时间:2013-11-09 23:09:19

标签: python macos tkinter osx-mavericks

我创建了一个简单的应用程序,使用Tkinter的Canvas小部件显示数据的散点图(参见下面的简单示例)。绘制10,000个数据点后,应用程序变得非常迟缓,可以通过尝试更改窗口大小来看到。

我意识到添加到Canvas的每个项目都是一个对象,所以在某些时候可能会出现一些性能问题,但是,我希望这个级别远高于10,000个简单的椭圆形对象。此外,我可以在绘制点或与它们交互时接受一些延迟,但在绘制它们之后,为什么只是调整窗口的大小会这么慢?

在阅读effbot's performance issues with the Canvas widget之后,似乎在调整大小期间可能会有一些不必要的连续空闲任务需要忽略:

  

Canvas小部件实现了直接的损坏/修复显示   模型。对画布和外部事件(如Expose)的更改是   都被视为对屏幕的“伤害”。小部件维护脏   矩形以跟踪受损区域。

     

当第一个伤害事件到来时,画布会注册一个空闲任务   (使用after_idle)用于“修复”画布时   程序返回到Tkinter主循环。您可以强制更新   调用update_idletasks方法。

因此,问题是,一旦数据被绘制,是否有任何方法可以使用update_idletasks使应用程序更具响应性?如果是这样,怎么样?

以下是最简单的工作示例。在加载窗口后尝试调整窗口大小,以查看应用程序的延迟程度。

更新

我最初在Mac OS X(Mavericks)中观察到这个问题,在调整窗口大小时,我的CPU使用率大幅上升。由Ramchandra的评论提示我在Ubuntu中测试了这个,但这似乎没有发生。也许这是一个Mac Python / Tk问题?不会是我遇到的第一个,请看我的另一个问题:

PNG display in PIL broken on OS X Mavericks?

有人也可以尝试使用Windows(我无法访问Windows框)吗?

我可以尝试使用我自己编译的Python版本在Mac上运行,看看问题是否仍然存在。

最小的工作示例:

import Tkinter
import random

LABEL_FONT = ('Arial', 16)


class Application(Tkinter.Frame):
    def __init__(self, master, width, height):
        Tkinter.Frame.__init__(self, master)
        self.master.minsize(width=width, height=height)
        self.master.config()
        self.pack(
            anchor=Tkinter.NW,
            fill=Tkinter.NONE,
            expand=Tkinter.FALSE
        )

        self.main_frame = Tkinter.Frame(self.master)
        self.main_frame.pack(
            anchor=Tkinter.NW,
            fill=Tkinter.NONE,
            expand=Tkinter.FALSE
        )

        self.plot = Tkinter.Canvas(
            self.main_frame,
            relief=Tkinter.RAISED,
            width=512,
            height=512,
            borderwidth=1
        )
        self.plot.pack(
            anchor=Tkinter.NW,
            fill=Tkinter.NONE,
            expand=Tkinter.FALSE
        )
        self.radius = 2
        self._draw_plot()

    def _draw_plot(self):

        # Axes lines
        self.plot.create_line(75, 425, 425, 425, width=2)
        self.plot.create_line(75, 425, 75, 75, width=2)

        # Axes labels
        for i in range(11):
            x = 75 + i*35
            y = x
            self.plot.create_line(x, 425, x, 430, width=2)
            self.plot.create_line(75, y, 70, y, width=2)
            self.plot.create_text(
                x, 430,
                text='{}'.format((10*i)),
                anchor=Tkinter.N,
                font=LABEL_FONT
            )
            self.plot.create_text(
                65, y,
                text='{}'.format((10*(10-i))),
                anchor=Tkinter.E,
                font=LABEL_FONT
            )

        # Plot lots of points
        for i in range(0, 10000):
            x = round(random.random()*100.0, 1)
            y = round(random.random()*100.0, 1)

            # use floats to prevent flooring
            px = 75 + (x * (350.0/100.0))
            py = 425 - (y * (350.0/100.0))

            self.plot.create_oval(
                px - self.radius,
                py - self.radius,
                px + self.radius,
                py + self.radius,
                width=1,
                outline='DarkSlateBlue',
                fill='SteelBlue'
            )

root = Tkinter.Tk()
root.title('Simple Plot')

w = 512 + 12
h = 512 + 12

app = Application(root, width=w, height=h)
app.mainloop()

2 个答案:

答案 0 :(得分:3)

TKinter和OS Mavericks的某些发行版实际上存在问题。显然你需要安装ActiveTcl 8.5.15.1。 TKinter和OS Mavericks存在一个错误。如果它仍然不快,那么下面会有更多技巧。

您仍然可以将多个点保存到一个图像中。如果你不经常改变它,它应该仍然更快。如果您更频繁地更改它们,可以使用以下其他方法来加速python程序。这个其他堆栈溢出线程谈论使用cython来制作更快的类。因为大多数减速可能是由于图形,这可能不会使它快得多,但它可能有所帮助。

Suggestions on how to speed up a distance calculation

你也可以通过事先定义迭代器(ex:iterator =(s.upper()for list in list_to_iterate_through))来加速for循环,但这被调用来绘制窗口,而不是在窗口维护时不断所以这应该不重要。另外,从python文档中提取速度的另一种方法是降低python背景检查的频率:

“Python解释器执行一些定期检查。特别是,它决定是否让另一个线程运行以及是否运行挂起的调用(通常是由信号处理程序建立的调用)。大部分时间都是无所事事,所以执行这些检查每次绕过解释器循环都会减慢速度。在sys模块中有一个函数setcheckinterval,你可以调用它来告诉解释器执行这些定期检查的频率。 Python 2.3默认为10.在2.3中,这被提升到100.如果你没有使用线程运行并且你不希望捕获很多信号,那么将其设置为更大的值可以提高解释器的性能,有时可以大大提高。“

我在网上找到的另一件事是,由于某种原因,通过更改os.environ ['TZ']来设置时间会使程序加速一点。

如果这仍然不起作用,那么TKinter可能不是最好的程序.Pygame可能更快,或者使用像开放式GL这样的显卡程序(我不认为可用于python,但是)

答案 1 :(得分:0)

Tk必须陷入所有这些椭圆形的循环中。我不是 确保画布曾经打算同时容纳这么多物品。

一种解决方案是将绘图绘制到图像对象中,然后放置图像 进入你的画布。