【问题标题】:Why is this Python method leaking memory?为什么这个 Python 方法会泄漏内存?
【发布时间】:2011-07-18 21:26:24
【问题描述】:

此方法遍历数据库中的术语列表,检查术语是否在作为参数传递的文本中,如果是,则将其替换为以术语为参数的搜索页面的链接。

术语的数量很多(大约 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))

我真的只有两个对象的引用可能是这里的嫌疑人:termsprocessed。但我看不出他们有任何理由不被垃圾收集。

编辑:

我想我应该说这个方法是在 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% 的 ram,最终将其填满并导致脚本崩溃并显示“无法分配内存” .

【问题讨论】:

  • &lt;Pendantic&gt; 在像 Python 这样的垃圾收集语言中几乎不可能泄漏内存。严格来说,内存泄漏是没有变量引用的内存。在 c++ 中,如果您在类中分配内存,但未声明析构函数,则可能会发生内存泄漏。你这里的只是高内存消耗。&lt;/Pendantic&gt;
  • :-) 好的。然后我得到了一个高内存消耗,每次调用后都会越来越高。但既然是方法。既然完成后我对任何东西都没有引用,为什么某些东西仍然会消耗内存?
  • 更新了这个问题。
  • 只是为了确保:您是否已验证此调用是造成内存消耗的原因?
  • 是的:删除它,内存保持静止。目前我在没有它的情况下运行,因为它对网站来说并不重要。

标签: python memory-leaks


【解决方案1】:

一旦你调用它,整个查询集就会被加载到内存中,这会消耗你的内存。如果结果集那么大,您希望获得大量结果,它可能会更多地访问数据库,但这意味着更少的内存消耗。

【讨论】:

  • 我对整个查询集都在内存中感到满意。数量不多:模型对象中最多包含 100000 个字符串。每次调用后,无论如何都应该进行垃圾收集。问题是每个调用累积地吃掉内存。第一次调用占用 3% 的 RAM。下一次调用 6% 以此类推。
  • 更新了这个问题。
【解决方案2】:

我完全找不到问题的原因,但现在我通过调用包含此方法调用的脚本(使用subprocess)来隔离臭名昭著的sn-p来传递它。内存增加了,但当然,在 python 进程死亡后会恢复正常。

说脏话。

但这就是我现在所得到的。

【讨论】:

    【解决方案3】:

    确保您没有在 DEBUG 中运行。

    【讨论】:

    • :-) 这是一个 prod 服务器,DEBUG 设置为 False。但是很好,这确实会导致内存泄漏,我你的回答迫使我检查。
    【解决方案4】:

    我想我应该说这个方法是在 Django 模型方法本身内部调用的。

    @类方法

    为什么?为什么是这个“类级别”

    为什么这些普通的方法不能有普通的范围规则并且——在正常的事件过程中——得到垃圾收集?

    换句话说(以答案的形式)

    摆脱@classmethod

    【讨论】:

    • ^^ @classmethod 用于类方法,在 update_html_description(self, links=3, queryset=None) 中调用,这是一个实例方法。这里没有混淆。
    • @e-satis。我确定没有混淆。也没有需要
    • 我添加了它们所属的类的名称以澄清事情:SearchedTerm 有一个链接任何文本的类方法,而 Video 实例使用它来更新它们的 html_description。 add_search_links_to_text 是一个类方法的原因当然是它是一个实用方法,并不意味着对 SearchedTerm 实例起作用。
    • 因为你是 124K 代表,我只是通过将所有类方法转换为实例方法来测试代码。它没有改变任何东西。鉴于你相当激进,给出了错误的答案,而且我已经看到你在 SO 上多次这样做,所以对你来说是 -1。
    • 在我上一条评论中谈到了聊天建议。放松伙计。如果你有这样的压力,你会心脏病发作。
    猜你喜欢
    • 2019-02-09
    • 2011-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    相关资源
    最近更新 更多