Python - работа с утечками памяти

У меня есть программа Python, которая проводит серию экспериментов, без данных, предназначенных для хранения из одного теста в другой. Мой код содержит утечку памяти, которую я полностью не могу найти (я смотрю другие темы на утечки памяти). Из-за ограничений по времени мне пришлось отказаться от обнаружения утечки, но если бы я смог изолировать каждый эксперимент, программа, вероятно, работала бы достаточно долго, чтобы получить нужные мне результаты.

  • Будет ли запускать каждый тест в отдельном потоке?
  • Существуют ли какие-либо другие способы выделения эффектов утечки?

Подробно о конкретной ситуации

  • Мой код состоит из двух частей: экспериментального бегуна и фактического кода эксперимента.
  • Хотя между кодами для запуска всех экспериментов и кодом, используемым каждым экспериментом, не используются общие глобальные подпрограммы, некоторые классы/функции обязательно разделяются.
  • Экспериментальный бегун - это не просто простой цикл, который можно легко поместить в оболочку script. Сначала он решает о тестах, которые необходимо выполнить с учетом параметров конфигурации, затем запускает тесты, затем выводит данные определенным образом.
  • Я пытался вручную вызвать сборщик мусора, если проблема была в том, что сбор мусора не запускался, но это не сработало.

Обновление

Ответ Gnibbler фактически позволил мне узнать, что мои объекты ClosenessCalculation, которые хранят все данные, используемые во время каждого вычисления, не уничтожаются. Затем я использовал это, чтобы вручную удалить некоторые ссылки, которые, похоже, исправили проблемы с памятью.

Ответ 1

Вы можете использовать что-то вроде этого, чтобы отслеживать утечки памяти

>>> from collections import defaultdict
>>> from gc import get_objects
>>> before = defaultdict(int)
>>> after = defaultdict(int)
>>> for i in get_objects():
...     before[type(i)] += 1 
... 

теперь предположим, что тесты утечки некоторой памяти

>>> leaked_things = [[x] for x in range(10)]
>>> for i in get_objects():
...     after[type(i)] += 1
... 
>>> print [(k, after[k] - before[k]) for k in after if after[k] - before[k]]
[(<type 'list'>, 11)]

11, потому что мы пропустили один список, содержащий еще 10 списков

Ответ 2

Темы не помогли бы. Если вы должны отказаться от обнаружения утечки, то единственным решением, которое будет содержать его эффект, будет время от времени запускать новый процесс (например, когда тест оставил общее потребление памяти слишком высоким по своему вкусу - вы можете определить размер виртуальной машины легко, прочитав /proc/self/status в Linux и другие подобные подходы к другим ОС).

Убедитесь, что общий script принимает необязательный параметр, чтобы сообщить ему, какой номер теста (или другой идентификатор теста) должен начаться, так что, когда один экземпляр script решает, что он занимает слишком много памяти, он может сообщите своему преемнику, откуда следует перезапустить.

Или, более прочно, убедитесь, что по мере завершения каждого теста его идентификация добавляется к некоторому файлу с известным именем. Когда программа запускается, она начинается с чтения этого файла и, таким образом, знает, какие тесты уже выполнены. Эта архитектура более надежна, поскольку она также охватывает случай, когда программа вылетает во время теста; конечно, чтобы полностью автоматизировать восстановление после таких сбоев, вы захотите, чтобы отдельная программа сторожевого таймера и процесс отвечали за запуск нового экземпляра тестовой программы, когда она определяет предыдущую, разбилась (она могла использовать subprocess для цель - ему также нужно указать, когда последовательность будет закончена, например, нормальный выход из тестовой программы может означать, что, хотя любой сбой или выход со статусом!= 0 означают необходимость запуска нового нового экземпляра).

Если эти архитектуры понравятся, но вам нужна дальнейшая помощь в их реализации, просто прокомментируйте этот ответ, и я буду рад предоставить пример кода - я не хочу делать это "превентивно", если есть еще -небыстрые проблемы, которые делают архитектуры непригодными для вас. (Это также может помочь узнать, на каких платформах вам нужно работать).

Ответ 3

Я бы просто реорганизовал эксперименты на отдельные функции (если не так, как это уже), тогда примите номер эксперимента из командной строки, который вызывает функцию одного эксперимента.

Просто перетащить оболочку script следующим образом:

#!/bin/bash

for expnum in 1 2 3 4 5 6 7 8 9 10 11 ; do
    python youProgram ${expnum} otherParams
done

Таким образом, вы можете оставить большую часть своего кода как есть, и это позволит устранить любые утечки памяти, которые, по вашему мнению, у вас есть между каждым экспериментом.

Конечно, лучшим решением всегда является поиск и устранение основной причины проблемы, но, как вы уже сказали, это не вариант для вас.

Хотя трудно представить себе утечку памяти в Python, я возьму ваше слово на этом - вы можете хотя бы рассмотреть возможность того, что вы ошибаетесь там. Подумайте, поднимая это в отдельном вопросе, с чем мы можем работать с низким приоритетом (в отличие от этой быстрой версии).

Обновление: создание сообщества wiki, поскольку вопрос несколько изменился с оригинала. Я бы удалил ответ, но по-прежнему считаю, что это полезно - вы могли бы сделать то же самое с вашим экспериментатором, как я предложил bash script, вам просто нужно убедиться, что эксперименты являются отдельными процессами, так что утечки памяти не происходят (если утечки памяти находятся в бегуне, вам придется выполнять анализ основных причин и исправлять ошибку).

Ответ 4

У меня была такая же проблема с третьей библиотекой C, которая протекала. Самая чистая работа, о которой я могла думать, - это развить и подождать. Преимущество этого заключается в том, что вам даже не нужно создавать отдельный процесс после каждого прогона. Вы можете определить размер вашей партии.

Здесь общее решение (если вы когда-либо находите утечку, единственное изменение, которое вам нужно сделать, это изменить run() для вызова run_single_process() вместо run_forked(), и вы сделаете это):

import os,sys
batchSize = 20

class Runner(object):
    def __init__(self,dataFeedGenerator,dataProcessor):
        self._dataFeed = dataFeedGenerator
        self._caller = dataProcessor

    def run(self):
        self.run_forked()

    def run_forked(self):
        dataFeed = self._dataFeed
        dataSubFeed = []
        for i,dataMorsel in enumerate(dataFeed,1):
            if i % batchSize > 0:
                dataSubFeed.append(dataMorsel)
            else:
                self._dataFeed = dataSubFeed
                self.fork()
                dataSubFeed = []
                if self._child_pid is 0:
                    self.run_single_process()
                self.endBatch()

    def run_single_process(self)
        for dataMorsel in self._dataFeed:
            self._caller(dataMorsel)

    def fork(self):
        self._child_pid = os.fork()

    def endBatch(self):
        if self._child_pid is not 0:
            os.waitpid(self._child_pid, 0)
        else:
            sys.exit() # exit from the child when done

Это изолирует утечку памяти дочернему процессу. И он никогда не будет течь больше раз, чем значение переменной batchSize.