了解 Tkinter Canvas 的性能限制

Posted

技术标签:

【中文标题】了解 Tkinter Canvas 的性能限制【英文标题】:Understanding performance limitations of the Tkinter Canvas 【发布时间】:2013-11-21 22:50:10 【问题描述】:

我创建了一个简单的应用程序来使用 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 的 cmets 的提示下,我在 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()

【问题讨论】:

无法重现;我什至在调整大小时检查了 CPU 使用率,但没有一个非常高。 而且,在调整窗口大小时不会出现延迟?什么操作系统? Python 的版本? 我在调整窗口大小时没有延迟。我正在使用 Ubuntu 13.04、Python 2.7 和 Tk 8.5。你用的是什么版本? 谢谢,这是有用的信息。我正在使用 Mac OS X (Mavericks)、Python 2.7.5、Tk 8.5。 在调整大小以及使用另一个程序覆盖和打开窗口时,我确实会遇到延迟和处理器峰值。如果我使用 100 分,它并不明显。此外,使用update_idletasks() 仅在初始绘图期间可能有所帮助,并且不会影响以后重新绘制画布。这是在 Win Vista、Python 2.7.2、Tk 8.5 上。 【参考方案1】:

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

您仍然可以将多个点保存到一张图像中。如果您不经常更改它,它应该仍然更快。如果您更频繁地更改它们,这里有一些其他方法可以加快 python 程序。这个另一个堆栈溢出线程讨论了使用 cython 来创建一个更快的类。因为大部分速度变慢可能是由于图形造成的,所以这可能不会让它更快,但它可能会有所帮助。

Suggestions on how to speed up a distance calculation

您还可以通过预先定义一个迭代器(例如:iterator = (s.upper() for s in list_to_iterate_through))来加速 for 循环,但这被调用来绘制窗口,而不是在维护窗口时一直调用,所以这应该不是很重要。另外,另一种加快速度的方法,取自 python 文档,是降低 python 背景检查的频率:

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

我在网上发现的另一件事是,由于某种原因,通过更改 os.environ['TZ'] 来设置时间会稍微加快程序的速度。

如果这仍然不起作用,那么 TKinter 可能不是执行此操作的最佳程序。Pygame 可能更快,或者像 open GL 这样使用显卡的程序(我不认为可用于 python,但是)

【讨论】:

+1 感谢您的回答。我会尝试其中的一些。我也很好奇您是否有 Tkinter 错误的参考或链接?赏金将在几个小时内完成,如果没有其他结果,我会将其奖励给这个答案。 另外,我同意 Tkinter 可能不是最好的。我以为我会研究 wxPython,但演示在某些示例上崩溃了……这不是一个好兆头!我也考虑过 Kivy,你觉得呢? @Fiver 我建议你使用Qt的QGraphicsView。我记得一个使用它的演示,其中可能涉及画布中的一百万个项目。 Kivy 应该可以正常工作,我从未使用过它,但它应该可以相对快速地运行。【参考方案2】:

Tk 必须在所有这些椭圆上循环时陷入困境。我不是 确定画布曾经打算一次容纳这么多物品。

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

【讨论】:

谢谢,我已经考虑过了,但我希望能够与点进行交互,即单击一个以选择它等。另外,如何解释这个问题没有t 发生在 Ubuntu 中?

以上是关于了解 Tkinter Canvas 的性能限制的主要内容,如果未能解决你的问题,请参考以下文章

tkinter画布窗口未从Powershell打开

更新 tkinter 中的滚动条

tkinter笔记:画布canvas

Python3 tkinter,怎么在Label/Canvas中插入图片?

用Python中的tkinter模块作图(续)

Spyder 中的 tkinter