Построение в цикле с использованием Matplotlib и Python работает все медленнее и медленнее с течением времени из-за утечки памяти

Я пытаюсь показать данные о напряжении в реальном времени, используя динамический график, который показывает записанное напряжение с течением времени и отображает график внутри окна Tkinter, в котором есть другие виджеты. Эта программа также должна выполнять различные действия с переключателями и реле на основе измеренных значений, а также отправлять уведомления об этих действиях с помощью текстовых сообщений и электронных писем. После многих итераций по различным методам я остановился на Matplotlib, используя clear () и draw () в каждом цикле, чтобы обновить график и поместить только отправку электронной почты в подпроцесс, чтобы интернет-задержка не останавливала выборку напряжения для длительный период времени. Он работает довольно хорошо, отображая 3 трассы по 500 точек каждая с частотой обновления графика примерно 1/4 секунды на Raspberry Pi 4.

Однако, когда я позволил программе работать, я обнаружил, что время цикла становится все длиннее и дольше, замедляя время цикла с 1/4 секунды до 2,5 секунд после 16 часов. Кроме того, размер виртуальной памяти увеличился со 105 МБ до 500 МБ.

Я добавил код, чтобы изолировать виновника, и сузил его до вызова clear () в Matplotlib. Вот график, показывающий время, которое потребовалось для каждого компонента цикла за 3 часа работы программы в цикле while.

Временные компоненты каждого цикла во времени

Здесь вы можете видеть, что время, необходимое для вызова clear () (красная линия), увеличилось с 0,055 секунды до 0,7 секунды за 3 часа работы цикла. Все остальные компоненты цикла оставались в значительной степени постоянными, за исключением вызовов функции plot () с парой больших всплесков. Но время, затрачиваемое на вызов clear (), продолжает увеличиваться и неприемлемо. Скорее всего, это тоже виновник утечки памяти.

Я извлек часть своей программы, относящуюся к этой проблеме, в следующей программе. Его можно извлечь и запустить в python3. Он измеряет время, необходимое для вызовов clear (), plot () и draw () в каждом цикле цикла while, и динамически отображает его на экране. Вы можете видеть, что призыв к очистке медленно увеличивается. Эта программа также позволяет вам видеть влияние других действий в системе на время выполнения этих вызовов. Вы можете видеть, что простое перемещение мыши имеет эффект. Попробуйте открыть браузер или воспроизвести видео.

import time
from time import sleep
from datetime import datetime
import math                      # provides math functions
from tkinter import *               # provides GUI capability
from tkinter import ttk
from tkinter import messagebox
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

# Set root window to center of screen. Parameters are size of window itself.
def center_window(self, width=300, height=200):
    # get screen width and height
    screen_width = self.winfo_screenwidth()
    screen_height = self.winfo_screenheight()

    # calculate position x and y coordinates
    x = (screen_width/2) - (width/2)
    y = (screen_height/2) - (height/2)
    self.geometry('%dx%d+%d+%d' % (width, height, x, y))

def alldone(*args):
    global running
    running = False

root = Tk()                         # Create base window class as root
root.wm_title("Green Canyon APFM System Ver: 0.6")
center_window(root,1024,580)
running = True
root.protocol("WM_DELETE_WINDOW", alldone)       # Define routine to call if window closes

time_plot = []
clear_plot = []
plot_plot = []
draw_plot = []

figure3 = plt.Figure(figsize=(9.9,5.8), dpi=100)
ax3 = figure3.add_subplot(111)
ax3.plot(time_plot,clear_plot,"r-", label="clear() call")    # Red line
ax3.plot(time_plot,plot_plot,"g-", label="plot() calls")     # Green line
ax3.plot(time_plot,draw_plot,"b-", label="clear() call")     # Blue line
scatter3 = FigureCanvasTkAgg(figure3, root) 
scatter3.get_tk_widget().grid(column=0, row=0, rowspan=2)
ax3.legend(loc=6)
ax3.set_xlabel('Time (secs)')
ax3.set_ylabel('Task time (sec) for the calls')
ax3.set_title(' EndTime = '+datetime.now().strftime("%H:%M:%S"))
ax3.grid()
scatter3.draw()
loopclock = time.time()
pclock = 0.0

"""-------------------------------------------------------------------------------
   Main Loop
-------------------------------------------------------------------------------"""
t2=t3=t4=t5=t6=t7=t8=0.0
t2a=t3a=t4a=t5a=t6a=t7a=t8a=0.0
nn = 0
while running:
    c2 = time.time()
    """----------------------------------------------------------------------
    This segment update the plot on the screen
    ----------------------------------------------------------------------"""
    ax3.clear()
    c3 = time.time()
    t2 = c3 - c2
    ax3.plot(time_plot,clear_plot,"r-", label="clear() call")    # Red line
    ax3.plot(time_plot,plot_plot,"g-", label="plot() calls")    # Green line
    ax3.plot(time_plot,draw_plot,"b-", label="draw() call")     # Blue line
    c4 = time.time()
    t3 = c4 - c3

    ax3.legend(loc=6)
    c5 = time.time()
    t4 = c5 - c4
    ax3.set_xlabel('Time (secs)')
    ax3.set_ylabel('Voltage (V)')
    c6 = time.time()
    t5 = c6 - c5
    looptime = time.time() - loopclock
    loopclock = time.time()
    ax3.set_title('          EndTime = '+datetime.now().strftime("%H:%M:%S")+
                  "  LT="+f"{looptime:.2f}"+
                  f"\n  {nn:4d}|{t2:.3f}|{t3:.3f}|{t4:.3f}|{t5:.3f}|{t6:.3f}|{t7:.3f}|{t8:.3f}")
    ax3.grid()
    c7 = time.time()
    t6 = c7 - c6
    scatter3.draw()
    c8 = time.time()
    t7 = c8 - c7

    root.update()
    c9 = time.time()
    t8 = c9 - c8

    # print out the max values in every 15 second intervals for plotting
    t2a = max(t2,t2a)
    t3a = max(t3,t3a)
    t4a = max(t4,t4a)
    t5a = max(t5,t5a)
    t6a = max(t6,t6a)
    t7a = max(t7,t7a)
    t8a = max(t8,t8a)
    nn += 1
    if time.time() > pclock + 15.0:
        pclock = time.time() 
        print(f"{nn:5d},{t2a:.4f}, {t3a:.4f}, {t4a:.4f}, {t5a:.4f}, {t6a:.4f}, {t7a:.4f}, {t8a:.4f}")
        t2a=t2
        t3a=t3
        t4a=t4
        t5a=t5
        t6a=t6
        t7a=t7
        t8a=t8

    xtime = (time.time() + 2209132800) % 60.0
    if len(time_plot) >= 500:
        time_plot.pop(0)
        clear_plot.pop(0)
        plot_plot.pop(0)
        draw_plot.pop(0)
    if len(time_plot) > 0 and time_plot[-1] > xtime:          # If we are rolling over to the next minute, decrease all the old values by 1 minute
        for j in range(len(time_plot)):
            time_plot[j] -= 60.0

    time_plot.append(xtime)
    clear_plot.append(t2)
    plot_plot.append(t4)
    draw_plot.append(t7)

root.quit()
sys.exit()

Был бы очень признателен за любые указатели на то, как я могу исправить эту утечку процессора и памяти. Я нашел предложения по созданию графика в отдельном процессе, чтобы вся память извлекалась при завершении задачи. Но завершение задачи, которая выводится на экран в каждом цикле, может привести к тому, что дисплей погаснет или начнет мерцать. Интересно, есть ли лучший способ сделать динамическое построение графиков с использованием python и matplotlib. Я использую python 3.7 и Matplotlib 3.0.2.


person nanowiz    schedule 01.12.2019    source источник


Ответы (1)


Проблема решена. Я установил Matpoltlib 3.1.4, и проблема исчезла.

person nanowiz    schedule 02.12.2019