9

Tkinter の Canvas ウィジェットを使用して、データの散布図を表示する簡単なアプリケーションを作成しました (以下の簡単な例を参照)。10,000 個のデータ ポイントをプロットした後、アプリケーションは非常に遅くなり、ウィンドウのサイズを変更しようとすると表示されます。

キャンバスに追加された各アイテムはオブジェクトであるため、ある時点でパフォーマンスの問題が発生する可能性がありますが、そのレベルは 10,000 個の単純な楕円形のオブジェクトよりもはるかに高いと予想していました。さらに、ポイントを描画したり操作したりするときに多少の遅延を受け入れることはできますが、描画後にウィンドウのサイズを変更するだけでこんなに遅くなるのはなぜでしょうか?

Canvas ウィジェットでの effbot のパフォーマンスの問題を読んだ後、サイズ変更中に無視する必要のある不要な連続アイドル タスクがいくつかあるようです。

Canvas ウィジェットは、単純な損傷/修復表示モデルを実装しています。キャンバスへの変更、および Expose などの外部イベントはすべて、画面への「損傷」として扱われます。ウィジェットは、破損した領域を追跡するためにダーティな四角形を維持します。

最初の損傷イベントが到着すると、キャンバスは (after_idle を使用して) アイドル タスクを登録します。これは、プログラムが Tkinter メイン ループに戻ったときにキャンバスを「修復」するために使用されます。update_idletasks メソッドを呼び出すことで、更新を強制できます。

では、問題はupdate_idletasks、データがプロットされた後、アプリケーションの応答性を高めるために使用する方法があるかどうかです。もしそうなら、どのように?

以下は、最も簡単な作業例です。ロード後にウィンドウのサイズを変更して、アプリケーションの遅延を確認してください。

アップデート

この問題はもともと Mac OS X (Mavericks) で確認されたもので、ウィンドウのサイズを変更するだけで CPU 使用率が大幅に上昇しました。Ramchandra のコメントに促されて、Ubuntu でこれをテストしましたが、これは発生していないようです。おそらくこれは Mac Python/Tk の問題でしょうか? 私が遭遇したのは初めてではありません。他の質問を参照してください。

OS X Mavericks で壊れた PIL の PNG 表示?

誰かが 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 にはバグがあります。それでも十分に速くない場合は、以下にさらにいくつかのトリックがあります。

複数のドットを 1 つの画像に保存することもできます。あまり頻繁に変更しない場合でも、より高速になるはずです。それらをより頻繁に変更する場合は、Python プログラムを高速化する他の方法を次に示します。この別のスタック オーバーフロー スレッドでは、cython を使用してより高速なクラスを作成する方法について説明しています。速度低下のほとんどはおそらくグラフィックが原因であるため、これにより速度が大幅に向上することはありませんが、役立つ可能性があります。

距離計算を高速化する方法に関する提案

事前にイテレータ (例: iterator = (s.upper() for s in list_to_iterate_through) ) を定義することで for ループを高速化することもできますが、これはウィンドウを描画するために呼び出され、ウィンドウが維持されるため常に呼び出されるわけではありません。あまり重要ではないはずです。また、python docs から取得した速度を上げる別の方法は、python のバックグラウンド チェックの頻度を下げることです。

「Python インタープリターは、いくつかの定期的なチェックを実行します。特に、別のスレッドを実行させるかどうか、および保留中の呼び出し (通常はシグナル ハンドラーによって確立された呼び出し) を実行するかどうかを決定します。ほとんどの場合、何もすることはありません。 , そのため, インタプリタループの各パスでこれらのチェックを実行すると, 速度が低下する可能性があります. sys モジュールには関数 setcheckinterval があり, これらの定期チェックを実行する頻度をインタプリタに伝えるために呼び出すことができます. Python 2.3 のリリース以前デフォルトは 10 でした。2.3 では、これは 100 に引き上げられました。スレッドで実行しておらず、多くのシグナルをキャッチすることを期待していない場合は、これをより大きな値に設定すると、インタープリターのパフォーマンスが大幅に向上することがあります。

私がオンラインで見つけたもう1つのことは、何らかの理由で os.environ['TZ'] を変更して時間を設定すると、プログラムが少しスピードアップすることです。

それでもうまくいかない場合は、TKinter がこれを行うのに最適なプログラムではない可能性があります。ただし、python)

于 2013-11-19T04:14:45.770 に答える