9

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

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

在使用 Canvas 小部件阅读effbot 的性能问题后,似乎在调整大小期间可能会有一些不需要的连续空闲任务需要忽略:

Canvas 小部件实现了直截了当的损坏/修复显示模型。对画布的更改以及诸如 Expose 之类的外部事件都被视为对屏幕的“损坏”。小部件维护一个脏矩形以跟踪损坏区域。

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

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

下面是最简单的工作示例。尝试在加载后调整窗口大小以查看应用程序变得多么滞后。

更新

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

PIL 中的 PNG 显示在 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()
4

2 回答 2

4

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

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

关于如何加快距离计算的建议

您还可以通过预先定义一个迭代器(例如: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 这样的图形卡的程序(我认为这不适用于然而,蟒蛇)

于 2013-11-19T04:14:45.770 回答
1

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

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

于 2013-11-16T05:32:11.873 回答