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