Почему этот метод Python теряет память? - PullRequest
8 голосов
/ 19 июля 2011

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

Количество терминов велико (около 100000), поэтому процесс довольно медленный, но это нормально, так как он выполняется как задание cron.Однако это приводит к стремительному увеличению потребления памяти скриптами, и я не могу найти, почему:

class SearchedTerm(models.Model):

[...]

@classmethod
def add_search_links_to_text(cls, string, count=3, queryset=None):
    """
        Take a list of all researched terms and search them in the 
        text. If they exist, turn them into links to the search
        page.

        This process is limited to `count` replacements maximum.

        WARNING: because the sites got different URLS schemas, we don't
        provides direct links, but we inject the {% url %} tag 
        so it must be rendered before display. You can use the `eval`
        tag from `libs` for this. Since they got different namespace as
        well, we enter a generic 'namespace' and delegate to the 
        template to change it with the proper one as well.

        If you have a batch process to do, you can pass a query set
        that will be used instead of getting all searched term at
        each calls.
    """

    found = 0

    terms = queryset or cls.on_site.all()

    # to avoid duplicate searched terms to be replaced twice 
    # keep a list of already linkified content
    # added words we are going to insert with the link so they won't match
    # in case of multi passes
    processed = set((u'video', u'streaming', u'title', 
                     u'search', u'namespace', u'href', u'title', 
                     u'url'))

    for term in terms:

        text = term.text.lower()

        # no small word and make
        # quick check to avoid all the rest of the matching
        if len(text) < 3 or text not in string:
            continue

        if found and cls._is_processed(text, processed):
            continue

        # match the search word with accent, for any case
        # ensure this is not part of a word by including 
        # two 'non-letter' character on both ends of the word
        pattern = re.compile(ur'([^\w]|^)(%s)([^\w]|$)' % text, 
                            re.UNICODE|re.IGNORECASE)

        if re.search(pattern, string):
            found += 1

            # create the link string
            # replace the word in the description 
            # use back references (\1, \2, etc) to preserve the original
            # formatin
            # use raw unicode strings (ur"string" notation) to avoid
            # problems with accents and escaping

            query = '-'.join(term.text.split())
            url = ur'{%% url namespace:static-search "%s" %%}' % query
            replace_with = ur'\1<a title="\2 video streaming" href="%s">\2</a>\3' % url

            string = re.sub(pattern, replace_with, string)

            processed.add(text)

            if found >= 3:
                break

    return string

Вам, вероятно, понадобится и этот код:

class SearchedTerm(models.Model):

[...]

@classmethod
def _is_processed(cls, text, processed):
    """
        Check if the text if part of the already processed string
        we don't use `in` the set, but `in ` each strings of the set
        to avoid subtring matching that will destroy the tags.

        This is mainly an utility function so you probably won't use
        it directly.
    """
    if text in processed:
        return True

    return any(((text in string) for string in processed))

У меня действительно есть толькодва объекта со ссылками, которые могут быть подозреваемыми здесь: terms и processed.Но я не вижу причин, по которым они не будут собирать мусор.

РЕДАКТИРОВАТЬ:

Думаю, я должен сказать, что этот метод вызывается внутри самого метода модели Django.Я не знаю, уместно ли это, но вот код:

class Video(models.Model):

[...]

def update_html_description(self, links=3, queryset=None):
    """
        Take a list of all researched terms and search them in the 
        description. If they exist, turn them into links to the search
        engine. Put the reset into `html_description`.

        This use `add_search_link_to_text` and has therefor, the same 
        limitations.

        It DOESN'T call save().
    """
    queryset = queryset or SearchedTerm.objects.filter(sites__in=self.sites.all())
    text = self.description or self.title
    self.html_description = SearchedTerm.add_search_links_to_text(text, 
                                                                  links, 
                                                                  queryset)

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

Проблема не только в том, что он потребляет много памяти, но и в том, что он не освобождает ее:каждые вызовы занимают около 3% оперативной памяти, в конечном итоге заполняя его и сбивая скрипт с «невозможно выделить память».

Ответы [ 4 ]

3 голосов
/ 19 июля 2011

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

2 голосов
/ 19 июля 2011

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

Поговорим о грязном.

Но это все, что я получил сейчас.

1 голос
/ 19 июля 2011

убедитесь, что вы не работаете в DEBUG.

0 голосов
/ 19 июля 2011

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

@ classmethod

Почему?Почему этот «уровень класса»

Почему эти обычные методы, которые могут иметь обычные правила области видимости и - при обычном ходе событий - не собирать мусор?

Другими словами(в форме ответа)

Избавьтесь от @classmethod.

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