【问题标题】:Django Python Garbage Collection woesDjango Python垃圾收集问题
【发布时间】:2011-06-03 10:21:10
【问题描述】:

经过 2 天的调试,我确定了我的时间猪:Python 垃圾收集器。
我的应用程序在内存中保存了很多对象。而且效果很好。
GC 执行通常的回合(我没有使用默认阈值 (700, 10, 10))。
偶尔,在一个重要事务的中间,第 2 代扫描开始并审查我的约 150 万代第 2 代对象。
这需要 2 秒! 名义交易时间不到 0.1 秒。

我的问题是我该怎么办?
我可以关闭第 2 代扫描(通过设置非常高的阈值 - 这是正确的方法吗?)并且 GC 很听话。
我应该什么时候打开它们?
我们使用 Django 实现了一个 Web 服务,每个用户请求大约需要 0.1 秒。
理想情况下,我将在 用户 API 请求之间运行这些 GC gen 2 周期。但是我该怎么做呢?
我的视图以 return HttpResponse(), AFTER 结束,我想运行第 2 代 GC 扫描。
我怎么做?这种方法是否有意义?

我可以标记永远不需要被垃圾收集的对象,这样 GC 就不会在每个第二代循环中测试它们吗?
当 Django 服务器相对空闲时,如何配置 GC 以运行全扫描?

多个平台(Windows / Linux)上的 Python 2.6.6。

【问题讨论】:

  • “我的应用程序在内存中有很多对象”?怎么样?
  • 容器是标准字典。对象本身是我自己的类实例(从对象派生)或元组,其中一项是对所述类实例的引用(其余项是整数)。
  • 既然 Django Request 和 Reply 对象是瞬态的,你怎么能在内存中保存任何东西呢?
  • @S.Lott:例如通过将字典放在模块的命名空间中。并非所有内容都必须存在于请求/响应周期中。
  • @piquadrat:正确。还有其他方法。使用模块代替 Sessions 可能适合这个问题,也可能不适合。准确地知道发生了什么而不是猜测是很重要的。在没有更多证据的情况下,我不容易相信这是 GC。

标签: python django garbage-collection


【解决方案1】:

我们为 gunicorn 做了类似的事情。根据您使用的 wsgi 服务器,您需要在响应之后而不是之前找到正确的钩子。 Django 有一个request_finished 信号,但该信号仍然是预响应。

对于 gunicorn,您需要在配置中定义 2 个方法,如下所示:

def pre_request(worker, req):
    # disable gc until end of request
    gc.disable()


def post_request(worker, req, environ, resp):
    # enable gc after a request
    gc.enable()

这里的post_request 在 http 响应发送后运行,因此是垃圾回收的好时机。

【讨论】:

    【解决方案2】:

    我相信一种选择是完全禁用垃圾收集,然后按照此处的建议在请求结束时手动收集:How does the Garbage Collection mechanism work?

    我想您可以在 settings.py 文件中禁用 GC。

    如果您想在每个请求上运行 GarbageCollection,我建议您开发一些在 process response 方法中执行此操作的中间件:

    import gc
    class GCMiddleware(object):
        def process_response(self, request, response):
            gc.collect()
            return response
    

    【讨论】:

    • 虽然我还没有实现这个,但它看起来是正确的方法。
    • 不,这不是正确的方法,它在返回响应之前收集 GC,所以它仍然会阻止响应的返回
    【解决方案3】:

    另一种方法可能是完全禁用 GC,并配置 mod_wsgi(或您正在使用的任何东西)以更频繁地终止和重新启动进程。

    【讨论】:

      【解决方案4】:

      我的视图以返回 HttpResponse() 结束,之后我想运行第 2 代 GC 扫描。

      // turn off GC
      // do stuff
      resp = HttpResponse()
      // turn on GC
      return resp
      

      我不确定,但你可以用// spawn thread to turn on GC in 0.1 sec 代替//turn on GC。

      为了确保在处理请求之前不会发生 GC,如果线程生成不起作用,您需要修改 django 本身或使用某种 django 钩子,如 dcurtis 建议的那样。

      如果您正在处理对性能至关重要的代码,您可能还需要考虑使用手动内存管理语言(如 C/C++)来处理该部分,并使用 Python 简单地调用/查询它。

      【讨论】:

        【解决方案5】:

        基于@milkypostman 的方法,您可以使用 gevent。您希望每个请求调用一次垃圾收集,但@milkypostman 建议的问题是对 gc.collect() 的调用仍会阻止请求的返回。 Gevent 让我们立即返回并让 GC 运行在进程返回之后继续。

        首先在您的 wsgi 文件中确保使用 gevent 魔法东西修补所有内容并禁用垃圾收集。你可以设置gc.disable(),但是有些库有上下文管理器,在禁用它后会打开它(例如messagepack),所以0阈值更具粘性。

        import gc
        from gevent import monkey
        
        # Disable garbage collection runs
        gc.set_threshold(0)
        # Apply gevent monkey magic
        monkey.patch_all()
        

        然后像这样为 Django 创建一些中间件:

        from gc import collect
        import gevent
        
        class BaseMiddleware:
        
            def __init__(self, get_response):
                self.get_response = get_response
        
        
        class GcCollectMiddleware(BaseMiddleware):
            """Middleware which performs a non-blocking gc.collect()"""
        
            def __call__(self, request):
                response = self.get_response(request)
                gevent.spawn(collect)
                return response
        

        您将看到这里与之前建议的方法的主要区别在于 gc.collect() 被包裹在 gevent.spawn 中,这不会阻止返回 HttpResponse 并且您的用户会得到更快的响应!

        【讨论】:

        • 我还应该注意,将中间件添加到您的设置文件时,请确保它是最后一个中间件。
        猜你喜欢
        • 1970-01-01
        • 2011-02-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-21
        • 2014-03-31
        相关资源
        最近更新 更多