性能和优化

本文档概述了一些技术和工具,这些技术和工具可以帮助您更有效地运行Django代码——更快,并且使用更少的系统资源。

介绍

通常,首先要考虑的是编写 能工作的 代码,它的逻辑功能与产生预期输出所需的逻辑功能相同。然而,有时,这并不足以使代码像人们所希望的那样 高效地 工作。

在这种情况下,需要的是一些东西——在实践中,通常是一组东西——以提高代码的性能,而不影响或只影响其行为。

一般方法

你在为 什么 而优化?

清楚地理解你所说的“绩效”是什么很重要,因为它不仅仅是一个指标。

提高速度可能是程序最明显的目标,但有时可能会寻求其他性能改进,例如降低内存消耗或减少对数据库或网络的要求。

一个领域的改进通常会提高另一个领域的性能,但并不总是如此;有时甚至会牺牲另一个领域的性能。例如,一个程序速度的提高可能会导致它使用更多的内存。更糟糕的是,如果速度提高太过内存不足,以致于系统开始耗尽内存,那么你所做的弊大于利。

还有其他的权衡。你自己的时间是一个宝贵的资源,比CPU时间更宝贵。一些改进可能太难实现,或者可能影响代码的可移植性或可维护性。并非所有的性能改进都值得付出努力。

所以,你需要知道你的目标是什么样的性能改进,你也需要知道你有一个很好的理由去瞄准那个方向——而且你需要:

性能标竿

仅仅猜测或假设代码中存在效率低下的原因是没有好处的。

Django工具

django-debug-toolbar 是一个非常方便的工具,它可以深入了解你的代码正在做什么以及花费了多少时间。特别是它可以显示您的页面生成的所有 SQL 查询,以及每个查询所用的时间。

第三方面板也可用于工具栏,可以(例如)报告缓存性能和模板呈现时间。

第三方服务

有许多免费服务可以从远程 HTTP 客户端的角度分析并报告你站点页面的性能,从而有效地模拟实际用户的体验。

它们不能报告代码的内部情况,但可以提供站点总体性能的有用见解,包括在 Django 环境中无法充分衡量的方面。示例包括:

还有一些付费服务可以执行类似的分析,包括一些支持 Django 的服务,可以与你的代码库集成以更全面地分析其性能。

从一开始就把事情做好

优化中的一些工作涉及到解决性能缺陷,但有些工作可以包括在你将要做的事情中,作为甚至在你开始考虑提高性能之前就应该采用的良好实践的一部分。

在这方面,Python 是一种优秀的语言,因为外观优美且感觉正确的解决方案通常是性能最好的解决方案。 与大多数技能一样,学习“看起来正确”的内容需要练习,但是最有用的准则之一是:

在适当的级别工作

Django 提供了许多不同的方法来处理事情,但仅仅因为它可以用某种方式来做某件事,并不意味着它是最合适的方式。例如,你可能会发现可以在 QuerySet、Python 或模板中计算相同的东西——集合中的项目数。

但是,在较低级别而不是较高级别进行此工作几乎总是会更快。 在更高级别上,系统必须通过多层抽象和更多机器层来处理对象。

也就是说,数据库通常比 Python 快,Python 比模板语言快:

# QuerySet operation on the database
# fast, because that's what databases are good at
my_bicycles.count()

# counting Python objects
# slower, because it requires a database query anyway, and processing
# of the Python objects
len(my_bicycles)
<!--
Django template filter
slower still, because it will have to count them in Python anyway,
and because of template language overheads
-->
{{ my_bicycles|length }}

一般来说,最适合工作的级别是能够舒服编写代码的最低级别。

备注

上面的示例仅是说明性的。

首先,在实际案例中你需要考虑你的计数前后发生了什么,以确定 在特定情况下 的最佳做法。数据库优化文档描述了 在模板中计数会更好的一种情况

其次,还有其他的选择可以考虑:在现实生活中,{{ my_bicycles.count }},它直接从模板调用 QuerySetcount() 方法,可能是最合适的选择。

缓存

通常情况下,计算一个值是很昂贵的(也就是耗费资源,而且速度很慢),所以把这个值保存到快速访问的缓存中,为下次需要时做好准备,会有巨大的好处。

Django 具有完善的缓存框架以及其他较小的缓存功能,这是一项非常重要且功能强大的技术。

缓存框架

Django 的 缓存框架 通过保存动态内容使其不需要为每个请求计算,从而为性能提升提供了非常重要的机会。

为了方便起见,Django 提供了不同级别的缓存粒度:你可以缓存特定视图的输出,或者只缓存难以生成的部分,甚至可以缓存整个站点。

实施缓存不应该被看作是改进那些因为写得不好而表现不佳的代码的一种替代方法。它是产生性能良好的代码的最终步骤之一,而不是一条捷径。

cached_property

通常必须多次调用一个类实例的方法。 如果该方法很昂贵,那么这样做会很浪费。

使用 cached_property 装饰器保存属性返回的值; 下次在该实例上调用该函数时,它将返回保存的值,而不是重新计算它。 请注意,这仅适用于将 self 作为唯一参数的方法,并将该方法更改为属性。

某些 Django 组件也具有自己的缓存功能; 这些将在下面与那些组件相关的部分中讨论。

理解惰性

惰性 是一种与缓存互补的策略。 缓存通过保存结果来避免重新计算; 惰性会延迟计算,直到真正需要它为止。

惰性允许我们在事物被实例化之前,甚至在有可能实例化之前,就对其进行引用。这有很多用途。

例如,惰性翻译 可以在甚至不知道目标语言之前就使用,因为它直到真正需要翻译后的字符串(例如在渲染的模板中)时才发生。

惰性也是一种省力的方法,它首先要避免工作。也就是说,惰性的一个方面是在必须做的时候才做任何事情,因为它可能最终不是必须的。因此,惰性可能有性能影响,相关工作的成本越高,从惰性中获得的收益就越多。

Python 提供了许多用于惰性求值的工具,特别是通过 generatorgenerator expression 构造。值得阅读 Python 的惰性,以发现在代码中利用惰性模式的机会。

Django 中的惰性

Django 本身就很惰性。一个很好的例子可以在 QuerySets 的计算中找到。QuerySets 是懒惰的。因此,一个 QuerySet 以被创建、传递,并与其他 QuerySets 组合,而不需要实际到数据库中去获取它所描述的项目。被传来传去的是 QuerySet 对象,而不是最终需要从数据库中获取的项目集合。

另一方面,某些操作将强制计算 QuerySet。避免过早地对 QuerySet 进行计算,可以节省对数据库进行一次昂贵而不必要的访问。

Django 还提供了一个 keep_lazy() 这允许使用惰性参数调用的函数本身表现为惰性,仅在需要时才进行计算。因此,懒惰参数——可能是一个昂贵的参数——在真正需要的时候才会被调用计算。

数据库

数据库优化

Django 的数据库层提供了多种方法来帮助开发者从其数据库中获得最佳性能。数据库优化文档 收集了相关文档的链接,并添加了各种技巧,概述了尝试优化数据库使用率时要采取的步骤。

HTTP 性能

中间件

Django自带了一些有用的 中间件 ,可以帮助优化你的网站的性能。它们包括:

ConditionalGetMiddleware

增加对现代浏览器的支持,根据 ETagLast-Modified 头有条件的 GET 响应。如果需要,它还可以计算和设置 ETag。

GZipMiddleware

压缩对所有现代浏览器的响应,节省带宽和传输时间。请注意,GZipMiddleware 目前被认为是一个安全风险,容易受到攻击,使TLS/SSL提供的保护失效。更多信息请参见 GZipMiddleware 中的警告。

会话

使用缓存会话

使用缓存会话 可能是一种提高性能的方法,因为不需要从数据库等较慢的存储源加载会话数据,而是将经常使用的会话数据存储在内存中。

静态文件

静态文件,根据定义不是动态的,是优化增益的理想目标。

ManifestStaticFilesStorage

通过利用网络浏览器的缓存功能,你可以在初始下载后完全消除对给定文件的网络访问。

ManifestStaticFilesStorage静态文件 的文件名后附加一个与内容相关的标记,使浏览器可以安全地长期缓存这些文件,而不会丢失将来的更改——当文件更改时,标记也会更改,因此浏览器将自动重新加载文件。

“最小化”

一些第三方 Django 工具和包提供了“最小化”HTML、CSS 和 JavaScript 的能力。它们删除不必要的空白、换行符和注释,并缩短变量名,从而减小站点发布的文档的大小。

模板性能

注意:

  • 使用 {% block %} 比使用 {% include %}
  • 由许多小块组装而成的严重碎片化的模板会影响性能

缓存的模板加载器

启用 缓存的模板加载器 通常会大幅提高性能,因为它避免了每次需要渲染时都编译模板。

使用现有软件的不同版本

有时不妨检查一下你所使用的软件是否有不同的、性能更好的版本。

这些技术是针对那些想要提升已经充分优化的 Django 网站性能的高级用户。

然而,它们并不是解决性能问题的灵丹妙药,它们也不太可能给那些还没有正确完成更基本的事情的网站带来比边际收益更好的收益。

备注

值得重复的是:寻找你已经在使用的软件的替代品从来不是解决性能问题的第一个办法。当达到此优化级别时,你需要一个正式的基准测试解决方案。

较新的往往是——但并不总是——更好。

维护良好的软件的新版本效率较低是相当罕见的,但维护者不可能预料到每一个可能的用例——所以要注意新版本可能会有更好的表现,但不要假设它们总是会有更好的表现。

Django 本身也是如此。 后续的发行版对整个系统进行了许多改进,但是你仍应检查应用程序的实际性能,因为在某些情况下,你可能会发现更改意味着性能较差而不是更好。

Python 的新版本,以及 Python 包的新版本,通常也会有更好的表现——但要衡量,而不是假设。

备注

除非你在特定版本中遇到了异常的性能问题,否则你通常会在新版本中找到更好的特性、可靠性和安全性,而且这些好处远比你可能赢得或失去的任何性能都重要。

Django 模板语言的替代方案

对于几乎所有的情况来说,Django 内置的模板语言是完全足够的。然而,如果你的 Django 项目中的瓶颈似乎在于模板系统,而你已经用尽了其他机会来补救,那么第三方的替代方案可能就是答案。

Jinja2 可以提高性能,特别是在速度方面。

其他模板系统在共享 Django 模板语言的程度上有所不同。

备注

如果 你在模板中遇到性能问题,首先要做的是了解具体原因。使用替代的模板系统可能会更快,但同样的收益也可能不费吹灰之力,例如,模板中昂贵的处理和逻辑可以在视图中更有效地完成。

替代软件实现

也许值得检查一下你正在使用的 Python 软件是否已经提供了不同的实现,可以更快地执行同样的代码。

然而:大多数写得好的 Django 网站的性能问题并不在 Python 执行层面,而是在低效的数据库查询、缓存和模板上。如果你依赖于编写得不好的 Python 代码,你的性能问题不可能通过让它执行得更快来解决。

使用替代实现可能会带来兼容性、部署、可移植性或维护问题。不言而喻,在采用非标准实现之前,你应该确保它为你的应用程序提供足够的性能收益,以抵消潜在的风险。

有了这些注意事项,你就应该知道:

PyPy

PyPy 是 Python 本身的一个实现(“标准” Python 实现是用 C 语言)。PyPy 可以提供可观的性能提升,通常用于重量级应用。

PyPy 项目的一个关键目标是 与现有的 Python API 和库兼容 。Django 是兼容的,但你需要检查你依赖的其他库的兼容性。

Python 库的 C 语言实现

有些 Python 库也是用 C 语言实现的,速度可以快得多。他们的目标是提供相同的 API。请注意,兼容性问题和行为差异并非未知(也并非总是立竿见影的)。