Увеличение памяти, используемой matplotlib при построении в цикле - PullRequest
0 голосов
/ 22 октября 2019

Я использую matplotlib как часть приложения Python для анализа некоторых данных. Моя цель - перебрать контейнер объектов и для каждого из них создать график содержащихся данных. Вот список использованных версий:

  • Python 3.6.9
  • matplotlib 3.1.1
  • numpy 1.15.4

ПРОБЛЕМА

Проблема, с которой я сталкиваюсь, заключается в том, что объем памяти, используемой приложением Python, значительно увеличивается во время цикла, который генерирует графики, до такой степени, что компьютер становится почти не реагирующим.

Я упростил реализацию до простого кода, который можно запустить для воспроизведения поведения (это было вдохновлено публикацией, которую я нашел на matplotlib github во время поиска некоторых ответов в Интернете)

import logging
import matplotlib as mpl
import matplotlib.pyplot as plt
from memory_profiler import profile
from memory_profiler import memory_usage
import numpy as np

TIMES = 20
SAMPLES_PER_LINE = 20000
STATIONS = 100
np_ax = np.linspace(1, STATIONS, num=SAMPLES_PER_LINE)
np_ay = np.random.random_sample(size=SAMPLES_PER_LINE)

logging.basicConfig(level="INFO")

@profile
def do_plots_close(i):
    fig = plt.figure(figsize=(16, 10), dpi=60)
    for stn in range(STATIONS):
        plt.plot(np_ax, np_ay + stn)

    s_file_name = 'withClose_{:03d}.png'.format(i)
    plt.savefig(s_file_name)
    logging.info("Printed file %s", s_file_name)
    plt.close(fig)


@profile
def do_plots_clf(i):
    plt.clf()
    for stn in range(STATIONS):
        plt.plot(np_ax, np_ay + stn)

    s_file_name = "withCLF_{:03d}.png".format(i)
    plt.savefig(s_file_name)
    logging.info("Printed file %s", s_file_name)


def with_close():
    for i in range(TIMES):
        do_plots_close(i)


def with_clf():
    fig = plt.figure(figsize=(16, 10), dpi=60)
    for i in range(TIMES):
        do_plots_clf(i)
    plt.close(fig)


if __name__ == "__main__":
    logging.info("Matplotlib backend used: %s", mpl.get_backend())
    mem_with_close = memory_usage((with_close, [], {}))
    mem_with_clf = memory_usage((with_clf, [], {}))
    plt.plot(mem_with_close, label='New figure opened and closed at each loop')
    plt.plot(mem_with_clf, label='Single figure cleared at every loop')

    plt.legend()
    plt.title("Backend: {:s} - memory usage".format(mpl.get_backend()))
    plt.ylabel('MB')
    plt.grid()
    plt.xlabel('time [s * 0.1]')  # `memory_usage` logs every 100ms
    plt.show()

Есть 2 функции, которые отображают графики TIMES, каждая с SAMPLES линиями с SAMPLES_PER_LINE элементами (что несколько близко к размеру данных, с которыми я имею дело). do_plots_close() создает новую фигуру в начале каждого цикла и закрывает ее в конце;тогда как do_plots_clf() работает на том же рисунке, после чего выполняется очистка. memory_profiler используется для получения памяти, используемой двумя функциями, которая затем отображается в виде рисунка, который прикрепляется:

imagewith_close() and with_clf()">

ПросмотрИз рисунка видно, что ни в одной из функций память, занятая одной фигурой, не освобождается полностью после закрытия фигуры (для do_plots_close()) или очистки (для do_plots_clf()). Я также пытался удалить параметр figsize при создании фигуры, но я не увидел никакой разницы в результате .

ВОПРОС

Я ищу лучший способ решения этой проблемы и уменьшения объема памяти, используемой в функции сохранения графиков.

  • Я неправильно использую API-интерфейс matplotlib?
  • Есть ли способ управлять одним и тем же объемом данных, не испытывая при этом увеличения памяти?
  • Почему память, кажется, освобождается только после нескольких графиков?

Любая помощь и предложения приветствуются.

Спасибо.

1 Ответ

1 голос
/ 22 октября 2019

Использовать сборщик мусора :

  • Почему память, кажется, освобождается только после нескольких графиков?
    • мусорсбор периодический, если не принудительный.
  • Выполняется с PyCharm

Начальный график, без сборки мусора:

  • Пока яЯ не использую бэкэнд Qt5, начальные графики сравнимы.
    • Поэтому я предполагаю, что графики, созданные с помощью сборки мусора, также будут сравнимыми для этого примера.

enter image description here

Добавить gc.collect() к def do_plots_close(i):

import gc

def do_plots_close(i):
    fig = plt.figure(figsize=(16, 10), dpi=60)
    for stn in range(STATIONS):
        plt.plot(np_ax, np_ay + stn)

    s_file_name = 'withClose_{:03d}.png'.format(i)
    plt.savefig(s_file_name)
    logging.info("Printed file %s", s_file_name)
    plt.close(fig)
    gc.collect()

with garbage collection

Также добавьте gc.collect к def with_clf():

def with_clf():
    fig = plt.figure(figsize=(16, 10), dpi=60)
    for i in range(TIMES):
        do_plots_clf(i)
        gc.collect()
    plt.close(fig)

enter image description here

С Jupyter Labи ipykernel:

  • ipykernel не освобождает память, когда фигуры открываются и закрываются, даже при сборке мусора
  • ipykernel, кажется, выделяет память для фигуры для def with_clf, отвечая незначительноclf и сборщик мусора

сборщик мусора отсутствует

enter image description here

сборщик мусора

enter image description here

...