Django视图函数性能分析
前言
虽然根据经验来看,许多简单Django应用的性能问题都来源于数据库IO(而这可以通过聚合查询等手段进行优化),但premature optimization is the root of all evil,定位性能问题的具体位置仍然是最应该先做的事。
和其它相对独立的Python代码不同,Django的视图函数代码与Django框架的耦合度很高,这给对代码的性能分析带来了一些困难。但幸运的是,Django丰富的生态环境中有足够多的手段能让我们很方便地完成这件事。
注:本文所使用的运行环境为
Python 3.6+Django 2.1.7
Django Debug Toolbar
Django Debug Toolbar可能是Django生态环境中最知名、最强大的页面调试工具了(官方文档连接)。这个工具可以很方便地将:页面耗时、设置、请求/响应、模板、SQL操作等一系列信息以侧边栏的形式展示在网页右侧,官方宣传图如下。
由于该工具能够展示页面耗时、SQL操作耗时等信息(特别是后者),所以该工具在视图函数性能分析方面也能起到非常关键的作用。
其最小化的部署方式如下:
- 通过 - pip安装该工具- 1 
 2
 3- pip install django-debug-toolbar
 # 通过源代码安装开发版
 # pip install -e git+https://github.com/jazzband/django-debug-toolbar.git#egg=django-debug-toolbar
- 修改Django配置文件(默认为 - settings.py),包括修改- INSTALLED_APPS、- MIDDLEWARE项,新增- INTERNAL_IPS项。- 1 
 2
 3
 4
 5
 6
 7
 8- INSTALLED_APPS = [
 # ...
 'django.contrib.staticfiles', # 确保已配置静态文件
 # ...
 'debug_toolbar',
 ]
 STATIC_URL = '/static/'- 1 
 2
 3
 4
 5- MIDDLEWARE = [
 # ...
 'debug_toolbar.middleware.DebugToolbarMiddleware',
 # ...
 ]- 1 - INTERNAL_IPS = ['127.0.0.1'] # 只对本机访问启用
- 配置Django URL规则 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13- from django.conf import settings
 from django.urls import include, path
 urlpatterns = [
 # ...
 ]
 if settings.DEBUG: # 只在DEBUG模式下启用
 import debug_toolbar
 urlpatterns.append(
 path('__debug__/', include(debug_toolbar.urls))
 )
此时打开任意一个页面即可看到对应效果,如下图所示:
另外,对于返回JSON、XML等数据的接口型视图函数来说,默认情况下是没办法使用Django Debug Tool的。不过在本地调试时,我们可以通过返回一个空的HTML页面(只需包含一个闭合的body标签)来达到类似的效果,具体操作如下:
| 1 |  | 
cProfile+Django中间件
虽然Django Debug Toolbar是一个非常实用的性能分析工具,但它对于视图函数内部的代码具体执行耗时依然无能为力。如果想要进行更深层次的代码性能分析,就要使用我上一篇文章介绍的cProfile这类工具了。但与那篇文章中提到的使用方法不同,这次我们必须先编写Django中间件才能对Django视图函数进行性能分析。
注:本节内容灵感来自博文:How to profile Django views
- 编写中间件代码主体(假设位于 - django_profile/my_middleware.py)- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32- # coding:utf-8
 import cProfile
 import pstats
 from io import StringIO
 from django.conf import settings
 from django.core.exceptions import MiddlewareNotUsed
 from django.http.response import HttpResponse
 class ProfileMiddleware(object):
 def __init__(self, get_response):
 if not settings.DEBUG:
 raise MiddlewareNotUsed # 只在DEBUG模式下启用该中间件
 self.get_response = get_response
 def __call__(self, request):
 if 'profile' in request.GET: # 当请求URL中存在profile参数时进行性能分析
 profile = cProfile.Profile()
 profile.enable()
 self.get_response(request)
 profile.disable()
 ram_file = StringIO()
 sort_by = 'tottime'
 stats = pstats.Stats(profile, stream=ram_file)
 stats.strip_dirs().sort_stats(sort_by).print_stats()
 response = HttpResponse(ram_file.getvalue().encode('utf-8'), 'text/plain')
 else:
 response = self.get_response(request)
 return response
- 在Django配置文件中启用该中间件 - 1 
 2
 3
 4
 5- MIDDLEWARE = [
 # ...
 'django_profile.my_middleware.ProfileMiddleware',
 # ...
 ]
该中间件大概的思路为:在Django真正调用视图函数(23行,self.get_response(request))时,使用cProfile对该视图函数进行性能分析,并用分析结果打包成的HttpResponse取代视图函数的Response返回给Django(或者说用户)。当然,由于我加了一系列的限制,该流程只会在DEBUG模式下、且URL中包含profile参数时才会启用。
此时在任何一个页面URL(HTTP Parameters)中增加profile参数时,即可看到对应效果,如下图所示:
这里介绍的只是cPorfile中间件的一个简单用例。实际上cProfile与Django中间件均具有比较强的可定制性,yet-another-django-profiler就是一个思路类似但更加复杂和完善的项目,有兴趣的话可以自行了解。另外,从理论上来说,cProfile可以和任意一个有类似Django中间件模式的Python Web框架搭配来达成类似的效果,这里就不扩展来说了。