性能与优化¶
本文档概述了有助于提高 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 }}
,它直接从模板中调用QuerySet
的count()
方法,可能是最合适的选择。
缓存¶
通常,计算一个值会很昂贵(即资源密集且缓慢),因此将该值保存到一个易于访问的缓存中以备下次需要时使用会带来巨大的好处。
这是一个非常重要且强大的技术,因此 Django 包含了一个全面的缓存框架,以及其他一些较小的缓存功能。
缓存框架¶
Django 的缓存框架提供了非常显著的性能提升机会,方法是保存动态内容,以便无需为每个请求计算它。
为了方便起见,Django 提供了不同级别的缓存粒度:您可以缓存特定视图的输出,或者只缓存难以生成的片段,甚至可以缓存整个站点。
实施缓存不应被视为改进由于编写不当而性能不佳的代码的替代方法。它是产生高性能代码的最后步骤之一,而不是捷径。
cached_property
¶
通常需要多次调用类实例的方法。如果该函数很昂贵,那么这样做就会造成浪费。
使用cached_property
装饰器会保存属性返回的值;下次在该实例上调用该函数时,它将返回保存的值,而不是重新计算它。请注意,这仅适用于将self
作为其唯一参数的方法,并且它会将方法更改为属性。
某些 Django 组件也有自己的缓存功能;这些将在下面与这些组件相关的部分中讨论。
理解惰性¶
惰性是一种与缓存互补的策略。缓存通过保存结果来避免重新计算;惰性将计算延迟到实际需要时。
惰性允许我们在实例化事物之前甚至在有可能实例化事物之前就引用它们。这有无数用途。
例如,延迟翻译可以在目标语言甚至未知之前使用,因为它直到实际需要翻译的字符串(例如在渲染的模板中)时才会发生。
惰性也是一种通过尝试避免工作来节省精力的方法。也就是说,惰性的一方面是在不得不做之前不做任何事情,因为毕竟它可能最终并不需要。因此,惰性会对性能产生影响,并且所涉及的工作越昂贵,通过惰性获得的收益就越大。
Python 提供了许多用于惰性求值的工具,特别是通过生成器和生成器表达式构造。值得了解 Python 中的惰性,以发现机会在您的代码中使用惰性模式。
Django 中的惰性¶
Django 本身非常惰性。一个很好的例子可以在QuerySets
的求值中找到。QuerySets 是惰性的。因此,可以创建QuerySet
,将其传递并与其他QuerySets
结合,而无需实际访问数据库来获取其描述的项目。传递的是QuerySet
对象,而不是最终将从数据库中需要的项目集合。
另一方面,某些操作将强制执行 QuerySet 的求值。避免过早求值QuerySet
可以节省访问数据库的昂贵且不必要的开销。
Django 还提供了一个 keep_lazy()
装饰器。这允许一个使用延迟参数调用的函数本身也表现出延迟行为,只有在需要时才进行计算。因此,延迟参数(可能是昂贵的参数)只有在严格需要时才会被调用进行计算。
数据库¶
数据库优化¶
Django 的数据库层提供了多种方法来帮助开发者从他们的数据库中获得最佳性能。该 数据库优化文档 收集了与相关文档的链接,并添加了各种提示,概述了在尝试优化数据库使用时需要采取的步骤。
HTTP 性能¶
中间件¶
Django 附带了一些有用的 中间件,可以帮助优化网站的性能。它们包括
ConditionalGetMiddleware
¶
为现代浏览器添加支持,以便根据 ETag
和 Last-Modified
标头有条件地获取响应。如果需要,它还会计算并设置 ETag。
GZipMiddleware
¶
压缩所有现代浏览器的响应,节省带宽和传输时间。请注意,GZipMiddleware 目前被认为存在安全风险,并且容易受到使 TLS/SSL 提供的保护失效的攻击。有关更多信息,请参阅 GZipMiddleware
中的警告。
会话¶
使用缓存会话¶
使用缓存会话 可能是提高性能的一种方法,因为它消除了从较慢的存储源(如数据库)加载会话数据的需要,而是将频繁使用的会话数据存储在内存中。
静态文件¶
静态文件(根据定义是非动态的)是优化增益的绝佳目标。
ManifestStaticFilesStorage
¶
通过利用 Web 浏览器的缓存功能,您可以在初始下载后完全消除对给定文件的网络访问。
ManifestStaticFilesStorage
将内容相关的标记附加到 静态文件 的文件名,以便浏览器可以安全地长期缓存它们而不会错过未来的更改——当文件更改时,标记也会更改,因此浏览器将自动重新加载资产。
“压缩”¶
一些 Django 的第三方工具和包提供了“压缩”HTML、CSS 和 JavaScript 的功能。它们删除不必要的空格、换行符和注释,并缩短变量名,从而减小网站发布的文档的大小。
模板性能¶
请注意
使用
{% block %}
比使用{% include %}
更快高度碎片化的模板(由许多小片段组成)会影响性能
缓存模板加载器¶
启用 cached template loader
通常可以显着提高性能,因为它避免了每次需要渲染模板时都编译模板。
使用可用软件的不同版本¶
有时值得检查您正在使用的软件是否有其他性能更好的版本可用。
这些技术针对的是希望突破已经优化良好的 Django 网站的性能极限的高级用户。
但是,它们并不是解决性能问题的灵丹妙药,对于那些尚未以正确的方式完成更基本操作的网站,它们不太可能带来超出边际收益的提升。
注意
值得重申的是:**寻求您正在使用的软件的替代方案永远不是解决性能问题的首选方案**。当您达到这种优化级别时,您需要一个正式的基准测试解决方案。
更新的版本通常(但并非总是)更好¶
维护良好的软件的新版本效率较低的现象相当罕见,但维护人员无法预料到所有可能的用例——因此,虽然要意识到较新版本可能性能更好,但不要假设它们总是会更好。
这对于 Django 本身也是如此。连续的版本在整个系统中都提供了一些改进,但您仍然应该检查应用程序的实际性能,因为在某些情况下,您可能会发现更改会导致其性能下降而不是提高。
Python 的更新版本以及 Python 包的更新版本通常也会性能更好——但要进行衡量,而不是假设。
注意
除非您在特定版本中遇到了异常的性能问题,否则您通常会在新版本中找到更好的功能、可靠性和安全性,并且这些好处远比您可能获得或损失的任何性能都重要。
Django 模板语言的替代方案¶
对于几乎所有情况,Django 的内置模板语言都完全足够。但是,如果 Django 项目中的瓶颈似乎在于模板系统,并且您已经用尽了其他解决此问题的方案,那么第三方替代方案可能是答案。
Jinja2 可以提供性能改进,尤其是在速度方面。
替代模板系统的共享 Django 模板语言的程度各不相同。
注意
如果您在模板中遇到性能问题,首先要做的就是准确了解原因。使用替代模板系统可能会更快,但同样的收益也可能在不费力的情况下获得——例如,模板中昂贵的处理和逻辑可以在视图中更有效地完成。
替代软件实现¶
值得检查您正在使用的 Python 软件是否已提供不同的实现,该实现可以更快地执行相同的代码。
但是:编写良好的 Django 网站中的大多数性能问题不在 Python 执行级别,而是在低效的数据库查询、缓存和模板中。如果您依赖于编写不佳的 Python 代码,则不太可能通过使其执行速度更快来解决性能问题。
使用替代实现可能会引入兼容性、部署、可移植性或维护问题。不言而喻,在采用非标准实现之前,您应该确保它提供的性能提升足以超过潜在的风险。
考虑到这些注意事项,您应该了解
PyPy¶
PyPy 是用 Python 本身实现的 Python(“标准”Python 实现是用 C 实现的)。PyPy 可以提供大量的性能提升,通常适用于重量级应用程序。
PyPy 项目的一个主要目标是 兼容 现有的 Python API 和库。Django 兼容,但您需要检查您依赖的其他库的兼容性。
Python 库的 C 实现¶
一些 Python 库也用 C 实现,并且可以快得多。它们旨在提供相同的 API。请注意,兼容性问题和行为差异并非闻所未闻(而且并不总是立即显而易见)。