效能與最佳化¶
本文件概述了可協助您更有效率地執行 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 是惰性的。因此,可以建立、傳遞和與其他 QuerySets
結合一個 QuerySet
,而實際上不會產生任何到資料庫的請求來獲取它描述的項目。傳遞的是 QuerySet
物件,而不是最終將從資料庫中要求的項目集合。
另一方面,某些操作會強制評估 QuerySet。避免過早評估 QuerySet
可以節省對資料庫進行昂貴且不必要的請求。
Django 還提供了一個 keep_lazy()
裝飾器。這允許一個使用惰性參數呼叫的函數本身也表現出惰性,只有在需要時才進行評估。因此,惰性參數(可能是昂貴的參數)只有在絕對需要時才被要求評估。
資料庫¶
資料庫最佳化¶
Django 的資料庫層提供了各種方式來幫助開發人員從他們的資料庫中獲得最佳效能。資料庫最佳化文件匯集了相關文件的連結,並添加了各種提示,概述了嘗試最佳化資料庫使用時應採取的步驟。
HTTP 效能¶
中介軟體¶
Django 配備了一些有用的中介軟體,可以幫助最佳化您的網站效能。它們包括
ConditionalGetMiddleware
¶
為現代瀏覽器添加對基於 ETag
和 Last-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(「標準」Python 實作是用 C 撰寫的)。PyPy 可以提供顯著的效能提升,通常適用於重量級應用程式。
PyPy 專案的一個關鍵目標是與現有的 Python API 和程式庫相容。Django 是相容的,但您需要檢查您依賴的其他程式庫的相容性。
Python 程式庫的 C 實作¶
一些 Python 程式庫也是用 C 實作的,而且速度可能快得多。它們旨在提供相同的 API。請注意,相容性問題和行為差異並非未知(而且並不總是立即顯而易見)。