1

我有一些简单的 python 代码可以生成一个实时监视器,用于查看每秒出现的数据。它使用 matplotlib 并且运行良好,只是存在内存泄漏。脚本的内存使用量在一天中缓慢上升,似乎没有限制。诚然,我对 python 编程很陌生,所以想知道是否有人能看到我正在做的事情,这显然很糟糕。提前感谢您的帮助。

import time
import numpy as np
import matplotlib
from matplotlib import figure
import matplotlib.pyplot as plt
import pylab as p
import os
import subprocess as sp
from subprocess import Popen, PIPE

def main():
    #####Initialize the plot#####
    fig = plt.figure()
    ax1 = fig.add_subplot(1,1,1,axisbg='black') #Set up basic plot attributes
    ax1.set_title('Blip Current vs. Time',color='blue')    
    ax1.set_xlabel('Time (hrs)',color='blue')
    ax1.set_ylabel('Blip Current',color='blue')
    for t in ax1.xaxis.get_ticklines(): t.set_color('yellow') 
    for t in ax1.xaxis.get_ticklabels(): t.set_color('yellow') 
    for t in ax1.yaxis.get_ticklines(): t.set_color('white') 
    for t in ax1.yaxis.get_ticklabels(): t.set_color('purple') 
    plt.ion() #Set interactive mode
    plt.show(False) #Set to false so that the code doesn't stop here
    i=0 #initialize counter variable (this will help me to limit the number of points displayed on graph

    ###Update the plot continuously###
    while True: #This is a cheap trick to keep updating the plot, i.e. create a real time data monitor
        blip=Popen('adoIf -vo -6 lxf.blip_b3 dataBarM', shell=True, stdout=PIPE).communicate()[0] #Get data to plot
        hr=float(time.strftime('%H'))
        mins=time.strftime('%M')
        secs=time.strftime('%S')
        secadj=float(secs)/3600
        minadj=float(mins)/60
        currenttime=float(hr+minadj+secadj) #Put time into format for easier plotting, i.e. 21.50 for 9:30 pm
        if currenttime >= 0 and currenttime < 0.22: #Set x range properly when rolling over to midnight
            xmin=0
            xmax=currenttime+.01
        else:
            xmin=currenttime-.22 #Limit data to be displayed, only care about recent past
            xmax=currenttime+.01
        try:
            blip =float(blip) #This throws an error if for some reason the data wasn't received at the top of the while statement
        except ValueError:
            blip=0.0
        if i>300: #Limit displayed points to save memory (hopefully...)
            del ax1.lines[0] #After 300 points, start deleting the first point each time
        else:
            i +=1
        if blip > 6: #Plot green points if current is above threshold
            ax1.plot(currenttime,blip,marker='o', linestyle='--',c='g')
        else: #Plot red points if current has fallen off
            ax1.plot(currenttime,blip,marker='o', linestyle='--',c='r')
        plt.axis([xmin,xmax,None,None]) #Set xmin/xmax to limit displayed data to a reasonable window
        plt.draw()
        time.sleep(2) #Update every 2 seconds

if __name__=='__main__':
    print 'Starting Monitor'
    main()
4

3 回答 3

3

尤里卡!我想通了(好吧,至少是一种解决方法)。我将 ax1.plot 命令从 while 循环中取出,而是使用 'set_xdata' 和 'set_ydata' 命令以及 fig.canvas.draw() 命令。感谢大家的帮助,特别是 reptilicus 指出 ax.plot 命令在我每次调用它时都会创建一个新对象。

要绘制的 x 和 y 值现在存储在数组中,每个数组中的第一个元素在 while 循环的每次迭代中被删除(在绘制一定数量的点之后,在代码中使用简单的索引号 i)。内存使用量持平,cpu 使用量更少。代码如下:

def main():
    #####Initialize the plot attributes#####
    fig = plt.figure()
    ax1 = fig.add_subplot(1,1,1, axisbg='black')#Set up basic plot attributes
    ax1.set_title('Blip Current vs. Time',color='blue')    
    ax1.set_xlabel('Time (hrs)',color='blue')
    ax1.set_ylabel('Blip Current',color='blue')
    for t in ax1.xaxis.get_ticklines(): t.set_color('yellow') 
    for t in ax1.xaxis.get_ticklabels(): t.set_color('yellow') 
    for t in ax1.yaxis.get_ticklines(): t.set_color('white') 
    for t in ax1.yaxis.get_ticklabels(): t.set_color('purple') 
    plt.ion() #Set interactive mode
    plt.show(False) #Set to false so that the code doesn't stop here
    i=0 #initialize counter variable (this will help me to limit the number of points displayed on graph

    ###Initialize x values####
    times=[] #Create blank array to hold x values
    hr=float(time.strftime('%H')) #Hours
    mins=time.strftime('%M') #Minutes
    secs=time.strftime('%S') #Seconds
    secadj=float(secs)/3600
    minadj=float(mins)/60
    currenttime=float(hr+minadj+secadj) #Put time into format for easier plotting, i.e. 21.50 for 9:30 pm
    times.append(currenttime) #Add first x value to x value array
    if currenttime >= 0 and currenttime < 0.22: #Set x range properly when rolling over to midnight
        xmin=0
        xmax=currenttime+.01
    else:
        xmin=currenttime-.22 #Limit data to be displayed, only care about recent past
        xmax=currenttime+.01

    ###Initialize y values###
    blipcur=[] #Create blank array to hold y values
    blip=Popen('adoIf -vo -6 lxf.blip_b3 dataBarM', shell=True, stdout=PIPE).communicate()[0] #Get first datapoint for plot
    try:
        blip =float(blip) #This throws an error if for some reason the data wasn't received at the top of the while statement
    except ValueError:
        blip=0.0
    blipcur.append(blip) #Add first y value to y value array

    ###Initialize plot###
    line1, = ax1.plot(times, blipcur, 'g-', marker='o')

    ###Update the plot continuously###
    while True: #This is a cheap trick to keep updating the plot, i.e. create a real time data monitor
        hr=float(time.strftime('%H')) #Get new x data for plotting (get current time)
        mins=time.strftime('%M')
        secs=time.strftime('%S')
        secadj=float(secs)/3600
        minadj=float(mins)/60
        currenttime=float(hr+minadj+secadj) #Put time into format for easier plotting, i.e. 21.50 for 9:30 pm
        times.append(currenttime) #Add latest point to x value array
        if currenttime >= 0 and currenttime < 0.22: #Set x range properly when rolling over to midnight
            xmin=0
            xmax=currenttime+.01
        else:
            xmin=currenttime-.22 #Limit data to be displayed, only care about recent past
            xmax=currenttime+.01

        blip=Popen('adoIf -vo -6 lxf.blip_b3 dataBarM', shell=True, stdout=PIPE).communicate()[0] #Get new y data for plotting
        try:
            blip =float(blip) #This throws an error if for some reason the data wasn't received from previous line of code
        except ValueError: #Just set to zero if there was some temporary error
            blip=0.0
        blipcur.append(blip) #Add latest point to y value array

        if i>285: #Limit data points shown on graph.  Saves memory.
            del blipcur[0] #Delete first element in y value array (oldest/first plotted point)
            del times[0] #Delete first element in x value array
        else:
            i +=1 #Only want to keep making number bigger until I get over the threshold for # points I want, then why bother

        line1.set_xdata(times) #Set x data for plot from x value array
        plt.axis([xmin,xmax,-2,50]) #Set xmin/xmax to limit displayed data to a reasonable window
        line1.set_ydata(blipcur) #Set y data for plot from y value array
        fig.canvas.draw() #Update plot
        time.sleep(2.6) #Update every 2.6 seconds

if __name__=='__main__':
    print 'Starting Monitor'
    main()
于 2013-07-08T11:16:11.103 回答
2

我很确定您每次都需要清除图形,否则 matplotlib 将继续创建一大堆新对象,并且不会收集垃圾。尝试类似:

fig.clf()

作为while循环中的第一件事。

于 2013-07-08T01:56:39.667 回答
0

您的原始代码显示您想要不同颜色的点,具体取决于blip. 使用您的新解决方案,使用set_data,您需要Line2D为每种颜色使用一个新的。另一种方法是使用散点图而不是线图。散点图可以为图中的每个点分配不同的颜色。


如果你想要一个固定大小的列表,比如 285 个元素,而不是这样做:

    if i>285: #Limit data points shown on graph.  Saves memory.
        del blipcur[0] #Delete first element in y value array (oldest/first plotted point)
        del times[0] #Delete first element in x value array

您可以将collection.dequesmaxlen=285. 使用deques,您可以随心所欲地添加,deque当它太满时,它会丢弃最旧的元素。这将使您的代码更简单一些,因为您不必自己管理它的大小。 deques也可以在 O(1) 时间内lists弹出第一个元素,而在 O(n) 时间内弹出第一个元素,因此这里也有理论上的性能提升,尽管只有大约 300 个元素它不会产生显着区别。


如果你有 Matplotlib 1.2 或更新版本,你可以使用它的FuncAnimation类。这将为您处理大部分样板代码。此外,它可以让您避免调用plt.ion(). (plt.ion()复杂的脚本不建议使用。)


import numpy as np
import matplotlib.pyplot as plt
import collections
import datetime as DT
import matplotlib.animation as animation


def currenttime():
    now = DT.datetime.now()
    hr = now.hour
    mins = now.minute / 60
    secs = now.second / 3600
    return float(hr + mins + secs)


def main():
    def animate(data, pathcol):
        xvals, yvals, colors = data
        assert len(xvals)<=300
        if len(xvals) > 1:
            ax.set_xlim(xvals.min(), xvals.max())
        pathcol.set_array(colors)
        pathcol.set_offsets(np.column_stack([xvals, yvals])) 

    def step():
        xvals = collections.deque([], maxlen=N)
        yvals = collections.deque([], maxlen=N)
        colors = collections.deque([], maxlen=N)
        fudge = 0
        while True:
            blip = np.random.random() * 10
            xvals.append(currenttime() + fudge)
            yvals.append(blip)
            colors.append(1 if blip > 6 else 0)
            yield np.asarray(xvals), np.asarray(yvals), np.asarray(colors)
            # make time go faster
            fudge += 0.0001

    # Initialize the plot#####
    N = 300
    fig = plt.figure()
    ax = fig.add_subplot(
        1, 1, 1, axisbg='black')  # Set up basic plot attributes
    ax.set_title('Blip Current vs. Time', color='blue')
    ax.set_xlabel('Time (hrs)', color='blue')
    ax.set_ylabel('Blip Current', color='blue')

    for t in ax.xaxis.get_ticklines():
        t.set_color('yellow')
    for t in ax.xaxis.get_ticklabels():
        t.set_color('yellow')
    for t in ax.yaxis.get_ticklines():
        t.set_color('white')
    for t in ax.yaxis.get_ticklabels():
        t.set_color('purple')
    pathcol = ax.scatter([0,24], [0,10],
                          c=[0,1], s=100,
                          cmap=plt.get_cmap('RdYlGn'), vmin=0, vmax=1)
    ani = animation.FuncAnimation(
        fig, animate, step, interval=20, fargs=(pathcol,))
    plt.show()


if __name__ == '__main__':
    print 'Starting Monitor'
    main()
于 2013-07-08T13:43:34.663 回答