web-dev-qa-db-ja.com

単純なPythonストップウォッチスクリプトが多くのコンピューターリソースを消費するのはなぜですか?

今日Pythonスクリプトをストップウォッチ用に使用し始めたところ、開いている他のすべてのもの(Firefox、Sublime Text、ターミナル)が大幅に遅くなっていることに気づきました。)システムモニターからストップウォッチスクリプトが通知されましたは私のCPUの約24%を使用しています。非常に些細なことがそれほど多くのリソースを使用するのは奇妙に思えます。

これを改善する方法についていくつかのアドバイスをいただけますか?私は本当にそれをバックグラウンドで実行して、さまざまなことに費やした時間を追跡したいと思っています。

スクリプトは次のとおりです。

#! /usr/bin/env python3
import tkinter
import time
import datetime
import numpy as np 
import subprocess

class StopWatch(tkinter.Frame):

    @classmethod
    def main(cls):
        tkinter.NoDefaultRoot()
        root = tkinter.Tk()
        root.title('Stop Watch')
        root.resizable(False, False)
        root.grid_columnconfigure(0, weight=1)
        root.geometry("200x235")
        padding = dict(padx=5, pady=5)
        widget = StopWatch(root, **padding)
        widget.grid(sticky=tkinter.NSEW, **padding)
        icon = tkinter.PhotoImage(file='stopwatch.ico')
        root.tk.call('wm', 'iconphoto', root._w, icon)
        root.mainloop()

    def __init__(self, master=None, cnf={}, **kw):
        padding = dict(padx=kw.pop('padx', 5), pady=kw.pop('pady', 5))
        super().__init__(master, cnf, **kw)

        self.grid_columnconfigure(0,weight=1)

        self.__total = 0
        self.start_time=datetime.datetime.now().strftime("%H:%M")
        self.start_date=datetime.datetime.now().strftime("%m/%d/%Y")
        self.start_dt=tkinter.StringVar(self, self.start_time+" "+self.start_date)

        self.__label = tkinter.Label(self, text='Session Time:')
        self.__time = tkinter.StringVar(self, '00:00')
        self.__display = tkinter.Label(self, textvariable=self.__time,font=(None, 26),height=2)
        self.__button = tkinter.Button(self, text='Start', relief=tkinter.RAISED, bg='#008000', activebackground="#329932", command=self.__click)
        self.__record = tkinter.Button(self, text='Record', relief=tkinter.RAISED, command=self.__save)
        self.__startdt = tkinter.Label(self, textvariable=self.start_dt)

        self.__label.grid   (row=0, column=0, sticky=tkinter.NSEW, **padding)
        self.__display.grid (row=1, column=0, sticky=tkinter.NSEW, **padding)
        self.__button.grid  (row=2, column=0, sticky=tkinter.NSEW, **padding)
        self.__record.grid  (row=3, column=0, sticky=tkinter.NSEW, **padding)
        self.__startdt.grid (row=4, column=0, sticky=tkinter.N, **padding)

    def __click(self):
        if self.__total==0:
            self.start_time=datetime.datetime.now().strftime("%H:%M")
            self.start_date=datetime.datetime.now().strftime("%m/%d/%Y")
            self.__time.set(self.start_time+" "+self.start_date)
        if self.__button['text'] == 'Start':
            self.__button['text'] = 'Stop'
            self.__button['bg']='#ff0000'
            self.__button['activebackground']='#ff3232'
            self.__record['text']='Record'
            self.__record['state']='disabled'
            self.__record['relief']=tkinter.SUNKEN
            self.__start = time.clock()
            self.__counter = self.after_idle(self.__update)
        else:
            self.__button['text'] = 'Start'
            self.__button['bg']='#008000'
            self.__button['activebackground']='#329932'
            self.__record['state']='normal'
            self.__record['relief']=tkinter.RAISED
            self.after_cancel(self.__counter)

    def __save(self):
        duration = int(self.__total//60)
        if duration > 0:
            subprocess.call("cp test_data.dat ./backup", Shell=True)
            data = np.loadtxt('test_data.dat', dtype="str")

            time_data = data[:, 0]
            date_data = data[:, 1]
            duration_data = data[:, 2]

            time_data=np.append(time_data,self.start_time)
            date_data=np.append(date_data,self.start_date)
            duration_data=np.append(duration_data,str(duration))

            new_data=np.column_stack((time_data,date_data,duration_data))
            np.savetxt('test_data.dat', new_data, header="*Time* | *Date* | *Duration*", fmt="%s")

            self.__record['text']='Saved'
        else:
            self.__record['text']='Not Saved'

        self.start_time=datetime.datetime.now().strftime("%H:%M")
        self.start_date=datetime.datetime.now().strftime("%m/%d/%Y")
        self.__time.set(self.start_time+" "+self.start_date)
        self.__total=0
        self.__time.set('00:00')

        self.__record['state']='disabled'
        self.__record['relief']=tkinter.SUNKEN


    def __update(self):
        now = time.clock()
        diff = now - self.__start
        self.__start = now
        self.__total += diff
        mins,secs=divmod(self.__total,60)
        self.__time.set('{:02.0f}:{:02.0f}'.format(mins,secs))
        self.start_dt.set(datetime.datetime.now().strftime("%H:%M %m/%d/%Y"))
        self.__counter = self.after_idle(self.__update)

if __name__ == '__main__':
    StopWatch.main()

2
Minh Tran

プロセッサがポーリング時間を無駄にしないようにする方法

あなたのスニペットで:

_def __update(self):
    now = time.clock()
    diff = now - self.__start
    self.__start = now
    self.__total += diff
    mins,secs=divmod(self.__total,60)
    self.__time.set('{:02.0f}:{:02.0f}'.format(mins,secs))
    self.start_dt.set(datetime.datetime.now().strftime("%H:%M %m/%d/%Y"))
    self.__counter = self.after_idle(self.__update)
_

制限なしでアイドル時に関数を再実行します。これは、プロセッサーがアイドルに時間を費やして時間を更新することを意味します。これにより、プロセッサの負荷がほぼ100%になります。 4つのコアのうち1つだけを使用するため、(ほぼ)25%が表示されます。

単に「スマート」な変数のwhileループを使用します。原則

time.sleep()を使用する場合、実際のプロセッサクロック時間を使用していないため、わずかなずれがあります。プロセッサは常にコマンドを処理するために少し時間を必要とするので、

_time.sleep(1)
_

実際には次のようなものになります

_time.sleep(1.003)
_

これは、それ以上のアクションなしでは、偏差の蓄積につながります、ただし

プロセスをスマートにすることができます。私がデスクトップアプリケーションで常に行うことは、必要な精度に応じて、1秒または1分ごとにsleep()を調整することです。サイクルが処理時間として使用するものは次のサイクルから取り消されるため、偏差の累積はありません。

原則として:

_import time

seconds = 0 # starttime (displayed)
startt = time.time() # real starttime
print("seconds:", seconds)

wait = 1

while True:
    time.sleep(wait)
    seconds = seconds + 1 # displayed time (seconds)
    target = startt + seconds # the targeted time
    real = time.time() # the "real" time
    calibration = real - target # now fix the difference between real and targeted
    nextwait = 1 - calibration # ...and retract that from the sleep of 1 sec
    wait = nextwait if nextwait >= 0 else 1  # prevent errors in extreme situation
    print("correction:", calibration)
    print("seconds:", seconds)
_

秒を単位として使用しているので、これで十分のようです。追加の負担:測定不能。

ターミナルでこのスニペットを実行すると、表示された時間と固定偏差の両方が表示されます。

_seconds: 0
correction: 0.02682352066040039
seconds: 1
correction: 0.036485910415649414
seconds: 2
correction: 0.06434035301208496
seconds: 3
correction: 0.07763338088989258
seconds: 4
correction: 0.037987709045410156
seconds: 5
correction: 0.03364992141723633
seconds: 6
correction: 0.07647705078125
seconds: 7
_

Whileの代わりにafter()を使用していますか?

同様に、 here で説明されているように、Tkinters after()メソッドを使用して、同じ時間を可変時間で調整して調整できます。


編集

リクエストに応じて:Tkinterのafter()メソッドを使用した例

固定ループ時間を使用する場合、次のようになります。

  1. ループ時間(時間分解能)は表示される時間単位のごく一部である必要があるため、リソースを必然的に浪費します。
  2. その場合でも、200ミリ秒のように、表示された時間は(ほぼ)200ミリ秒のリアルタイムとの差を示し、その後、次の表示された秒にジャンプするのが短すぎます。

after()を使用していて、上記の非GUIの例のように、以下の例のように可変タイムサイクルを使用する場合は、回答のスニペットとまったく同じオプションを提供します。

enter image description here

_#!/usr/bin/env python3
from tkinter import *
import time

class TestWhile:

    def __init__(self):

        # state on startup, run or not, initial wait etc
        self.run = False
        self.showtime = 0
        self.wait = 1000
        # window stuff
        self.window = Tk()
        shape = Canvas(width=200, height=0).grid(column=0, row=0)
        self.showtext = Label(text="00:00:00", font=(None, 26))
        self.showtext.grid(column=0, row=1)
        self.window.minsize(width=200, height=50)
        self.window.title("Test 123(4)")
        # toggle button Run/Stop
        self.togglebutton = Button(text="Start", command = self.toggle_run)
        self.togglebutton.grid(column=0, row=2, sticky=NSEW, padx=5, pady=5)
        self.resetbutton = Button(text="reset", command = self.reset)
        self.resetbutton.grid(column=0, row=3, sticky=NSEW, padx=5, pady=5)
        self.window.mainloop()

    def format_seconds(self, seconds):
        mins, secs = divmod(seconds, 60)
        hrs, mins = divmod(mins, 60)
        return '{:02d}:{:02d}:{:02d}'.format(hrs, mins, secs)

    def reset(self):
        self.showtime = 0
        self.showtext.configure(text="00:00:00")

    def toggle_run(self):
        # toggle run
        if self.run:
            self.run = False
            self.togglebutton.configure(text="Run")
            self.showtime = self.showtime - 1
            self.resetbutton.configure(state=NORMAL)
        else:
            self.run = True
            self.togglebutton.configure(text="Stop")
            self.resetbutton.configure(state=DISABLED)
            # prepare loop, set values etc
            self.showtext.configure(text=self.format_seconds(self.showtime))
            self.fix = self.showtime
            self.starttime = time.time()
            # Let's set the first cycle to one second
            self.window.after(self.wait, self.fakewhile)

    def update(self):
        self.window.after(self.wait, self.fakewhile)
        self.showtext.configure(text=str(self.format_seconds(self.showtime)))
        self.targeted_time = self.starttime + self.showtime
        self.realtime = time.time() + self.fix
        diff = self.realtime - self.targeted_time
        self.wait = int((1 - diff) * 1000)
        print("next update after:", self.wait, "ms")

    def fakewhile(self):
        self.showtime = self.showtime + 1
        if self.run:
            self.update()


TestWhile()
_

注意

...たとえば、2番目のスレッドからGUIを更新している場合Gtkアプリケーションの場合、常にアイドル状態から更新する必要があります。

6
Jacob Vlijm

ガイダンスを提供してくれたJacob Vlijmに感謝します。

以前のコードスニペットにtime.sleep()メソッドを組み込もうとしました。まったく機能しませんでした。そこで、tkinter after()メソッドを使用して、コード全体を書き直しました。私はそれの核をここに残して、このスレッドに出くわして、つまずいた人のために残しておきます。

After()メソッドを使用し、スクリプトを200ミリ秒待ってから関数を再度呼び出すと、CPUが解放され、スムーズなストップウォッチが可能になります。

編集:冗長な不良コードを削除します。 tkinterでタイマースクリプトを操作するのと同じことをしている場合は、上記のJacobのコメントを参照してください。

0
Minh Tran