Понимание ограничений производительности Tkinter Canvas

Я создал простое приложение для отображения диаграммы рассеяния данных с помощью виджета Tkinter Canvas (см. простой пример ниже). После построения 10 000 точек данных приложение становится очень медленным, в чем можно убедиться, попробовав изменить размер окна.

Я понимаю, что каждый элемент, добавленный в Canvas, является объектом, поэтому в какой-то момент могут возникнуть некоторые проблемы с производительностью, однако я ожидал, что этот уровень будет намного выше, чем 10 000 простых овальных объектов. Кроме того, я мог бы принять некоторые задержки при рисовании точек или взаимодействии с ними, но после того, как они нарисованы, почему простое изменение размера окна может быть таким медленным?

После прочтения о проблемах производительности effbot с виджетом Canvas кажется, что во время работы могут возникать ненужные непрерывные незанятые задачи. изменение размера, которое необходимо игнорировать:

Виджет Canvas реализует простую модель отображения повреждений/восстановления. Изменения на холсте и внешние события, такие как Expose, рассматриваются как «повреждение» экрана. Виджет поддерживает грязный прямоугольник, чтобы отслеживать поврежденную область.

Когда приходит первое событие повреждения, холст регистрирует бездействующую задачу (используя after_idle), которая используется для «восстановления» холста, когда программа возвращается к основному циклу Tkinter. Вы можете принудительно выполнить обновления, вызвав метод update_idletasks.

Итак, вопрос в том, есть ли способ использовать update_idletasks, чтобы сделать приложение более отзывчивым после того, как данные были нанесены на график? Если да, то как?

Ниже приведен простейший рабочий пример. Попробуйте изменить размер окна после его загрузки, чтобы увидеть, насколько медленным становится приложение.

Обновлять

Первоначально я наблюдал эту проблему в Mac OS X (Mavericks), где я получаю значительный всплеск загрузки ЦП при простом изменении размера окна. Под влиянием комментариев Рамчандры я проверил это в Ubuntu, и, похоже, этого не происходит. Возможно, это проблема Mac Python/Tk? Не будет первым, с кем я столкнулся, см. мой другой вопрос:

Отображение PNG в PIL не работает в OS X Mavericks?

Может ли кто-нибудь также попробовать в Windows (у меня нет доступа к Windows)?

Я могу попробовать запустить на Mac свою собственную скомпилированную версию Python и посмотреть, сохраняется ли проблема.

Минимальный рабочий пример:

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()

person Fiver    schedule 09.11.2013    source источник
comment
Невозможно воспроизвести; Я даже проверял использование ЦП при изменении размера, но ни один из них не был значительно высоким.   -  person Ramchandra Apte    schedule 13.11.2013
comment
И при изменении размера окна у вас не возникает задержек при изменении размера? Какая ОС? Версия питона?   -  person Fiver    schedule 13.11.2013
comment
У меня нет задержек при изменении размера окна. Я использую Ubuntu 13.04, Python 2.7 и Tk 8.5. Какие версии вы используете?   -  person Ramchandra Apte    schedule 13.11.2013
comment
Спасибо, это полезная информация. Я использую Mac OS X (Mavericks), Python 2.7.5, Tk 8.5.   -  person Fiver    schedule 13.11.2013
comment
Я получаю лаги и скачки процессора при изменении размера, а также при закрытии и открытии окна другой программой. Если я использую 100 баллов, это не заметно. Кроме того, использование update_idletasks(), возможно, помогает только при первоначальном рисовании и не влияет на последующую перерисовку холста. Это на Win Vista, Python 2.7.2, Tk 8.5.   -  person Todd    schedule 14.11.2013
comment
@Todd Спасибо за тестирование и подтверждение проблемы в Windows.   -  person Fiver    schedule 14.11.2013


Ответы (2)


На самом деле есть проблема с некоторыми дистрибутивами TKinter и OS Mavericks. Видимо нужно установить ActiveTcl 8.5.15.1. Есть баг с TKinter и OS Mavericks. Если это все еще недостаточно быстро, есть еще несколько трюков ниже.

Вы все еще можете сохранить несколько точек в одно изображение. Если вы не меняете его очень часто, он все равно должен работать быстрее. Если вы меняете их чаще, вот несколько других способов ускорить программу на Python. Этот другой поток переполнения стека говорит об использовании cython для создания более быстрого класса. Поскольку большая часть замедления, вероятно, связана с графикой, это, вероятно, не сделает его намного быстрее, но может помочь.

Предложения по ускорению расчета расстояния

вы также можете ускорить цикл for, заранее определив итератор ( например: iterator = (s.upper() for s in list_to_iterate_through)) , но это вызывается для рисования окна, а не постоянно, поскольку окно поддерживается, поэтому это не должно иметь большого значения. Кроме того, еще один способ ускорить работу, взятый из документации по python, — снизить частоту фоновых проверок python:

«Интерпретатор Python выполняет некоторые периодические проверки. В частности, он решает, разрешать ли выполнение другого потока и запускать ли ожидающий вызов (обычно вызов, установленный обработчиком сигнала). В большинстве случаев делать нечего. , поэтому выполнение этих проверок при каждом проходе цикла интерпретатора может замедлить работу.В модуле sys есть функция setcheckinterval, которую вы можете вызвать, чтобы сообщить интерпретатору, как часто выполнять эти периодические проверки.До выпуска Python 2.3 по умолчанию оно равно 10. В версии 2.3 это значение было увеличено до 100. Если вы не работаете с потоками и не ожидаете перехвата большого количества сигналов, установка большего значения может улучшить производительность интерпретатора, иногда существенно».

Еще одна вещь, которую я нашел в Интернете, заключается в том, что по какой-то причине установка времени путем изменения os.environ['TZ'] немного ускорит программу.

Если это все еще не работает, то, вероятно, TKinter — не лучшая программа для этого. Pygame может быть быстрее или программа, использующая видеокарту, например open GL (я не думаю, что она доступна для Питон, однако)

person trevorKirkby    schedule 19.11.2013
comment
+1 Спасибо за ваш ответ. Я попробую некоторые из них. Мне также любопытно, есть ли у вас ссылка или ссылка на ошибку Tkinter? Награда будет увеличена через пару часов, и если ничего другого не появится, я присужу ее этому ответу. - person Fiver; 19.11.2013
comment
Кроме того, я согласен, что Tkinter может быть не лучшим. Я думал, что стоит изучить wxPython, но демонстрация вылетает на некоторых примерах... нехороший знак! Я также рассматривал Kivy, что вы думаете? - person Fiver; 19.11.2013
comment
@Fiver Я предлагаю вам использовать Qt QGraphicsView. Я помню демонстрационную версию, в которой использовалось, вероятно, миллион элементов на холсте. - person Ramchandra Apte; 19.11.2013
comment
Kivy должен работать нормально, я никогда не использовал его, но он должен работать относительно быстро. - person trevorKirkby; 20.11.2013

Tk, должно быть, зацикливается на всех этих овалах. Я не уверен, что полотно когда-либо предназначалось для одновременного хранения такого количества предметов.

Одно из решений состоит в том, чтобы нарисовать свой график в объекте изображения, а затем поместить изображение на холст.

person Oblivion    schedule 16.11.2013
comment
Спасибо, я думал об этом, но я хотел бы иметь возможность взаимодействовать с точками, т. е. щелкнуть по одной из них, чтобы выбрать ее, и т. д. Кроме того, как объяснить, что проблема не возникает в Ubuntu? - person Fiver; 16.11.2013