Разбиране на ограниченията на производителността на Tkinter Canvas

Създадох просто приложение за показване на диаграма на разсейване на данни, използвайки изпълнимия модул Canvas на Tkinter (вижте простия пример по-долу). След начертаване на 10 000 точки от данни, приложението става много бавно, което може да се види, като се опитате да промените размера на прозореца.

Осъзнавам, че всеки елемент, добавен към Canvas, е обект, така че в даден момент може да има проблеми с производителността, но очаквах това ниво да бъде много по-високо от 10 000 прости овални обекта. Освен това, бих могъл да приема някои закъснения при изчертаване на точките или взаимодействие с тях, но след като те са начертани, защо просто преоразмеряването на прозореца ще бъде толкова бавно?

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

Уиджетът Canvas реализира директен модел на показване на повреди/поправки. Промените в платното и външни събития като Expose се третират като „повреди“ на екрана. Джаджата поддържа мръсен правоъгълник, за да следи повредената зона.

Когато пристигне първото събитие за повреда, платното регистрира неактивна задача (използвайки after_idle), която се използва за „поправяне“ на платното, когато програмата се върне към основния цикъл на Tkinter. Можете да принудите актуализациите, като извикате метода update_idletasks.

И така, въпросът е дали има някакъв начин да се използва update_idletasks, за да се направи приложението по-отзивчиво, след като данните са начертани? Ако е така, как?

По-долу е най-простият работещ пример. Опитайте да преоразмерите прозореца, след като се зареди, за да видите колко бавно става приложението.

Актуализация

Първоначално наблюдавах този проблем в Mac OS X (Mavericks), където получавам значителен скок в използването на процесора, когато просто преоразмерявам прозореца. Подтикнат от коментарите на Ramchandra, тествах това в 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
И при преоразмеряване на прозореца не получавате забавяне при преоразмеряване? Каква ОС? Версия на Python?   -  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 Предлагам ви да използвате QGraphicsView на Qt. Спомням си демонстрация, използваща го, която включваше вероятно милион елемента в платното. - 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