Python - Обойти утечки памяти - PullRequest
30 голосов
/ 29 октября 2009

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

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

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

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

Обновление

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

Ответы [ 4 ]

57 голосов
/ 29 октября 2009

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

>>> 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 списков

4 голосов
/ 29 октября 2009

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

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

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

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

3 голосов
/ 20 февраля 2012

У меня была такая же проблема со сторонней библиотекой 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.

2 голосов
/ 29 октября 2009

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

Сценарий оболочки просто выглядит следующим образом:

#!/bin/bash

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...