Почему мой простой скрипт секундомера Python занимает так много ресурсов компьютера? [закрыто]

Сегодня я начал использовать Python-скрипт для секундомера и заметил значительное замедление во всех других открытых мною вещах (Firefox, Sublime Text, Terminal). Системный монитор сообщает мне, что мой скрипт секундомера использует около 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
задан 14 May 2019 в 09:57

2 ответа

Как препятствовать тому, чтобы процессор сошел с ума при опросе времени

В Вашем отрывке:

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%. Так как это использует только один из четырех ядер, Вы будете видеть свои (почти) 25%.

Просто используйте "умный", переменный цикл с условием продолжения; принцип

Если мы использовали бы time.sleep(), так как мы не используем реальный процессор, показывают время, у нас было бы небольшое отклонение. Процессору всегда требуется немного времени для обработки команды, таким образом,

time.sleep(1)

на самом деле будет что-то как

time.sleep(1.003)

Это, без дальнейших действий, привело бы к накапливающемуся отклонению, однако:

Мы можем сделать процесс умным. То, что я всегда делаю в настольных приложениях, должно калибровать 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

Используя после (), вместо в то время как?

Аналогично, можно использовать Tkinters после () метод, как описано здесь, с помощью того же приема с переменным временем для калибровки.


Править

По запросу: пример с помощью Tkinter после () метод

При использовании фиксированного looptime Вы:

  1. неизбежно ресурсы waisting, так как Ваше время цикла (разрешение времени) должно быть небольшой частью отображенной единицы измерения времени.
  2. Даже если Вы сделаете, как Ваши 200 мс, то отображенное время будет во времена показывать различие с реальным временем (почти) 200 мс, впоследствии сопровождаемых слишком коротким переходом к следующей отображенной секунде.

Если Вы используете after(), и хочу использовать переменный цикл времени, как в non-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()

Примечание:

... это при обновлении GUI от второго потока в, например, приложения Gtk необходимо было бы всегда обновлять от неактивного.

6
ответ дан 14 May 2019 в 09:57

Спасибо Джейкоб Влейм за руководство.

Я попытался включить метод time.sleep () в предыдущий фрагмент кода. Это не сработало вообще. Поэтому я обратился к методу tkinter after () и полностью переписал код. Я оставлю суть здесь для того, кто придет и наткнется на эту нить.

Использование метода after () и сценария let ждет 200 мс, а затем повторный вызов функции освобождает мой ЦП и по-прежнему допускает прилично плавный секундомер.

РЕДАКТИРОВАТЬ: удалить лишние плохие коды. См. Комментарий Джейкоба выше, если вы находитесь в том же поиске работы сценариев таймера с tkinter.

0
ответ дан 14 May 2019 в 09:57

Другие вопросы по тегам:

Похожие вопросы: