使用DTrace和cProfile分析Django性能

Django是一個非常棒的框架,相當重要的原因是它包含了快速創建web應用程序所需的一切。但是開發者不應該是唯一的受益者。這個應用程序對用戶來說也應該更快。

官方文檔中有一章是關於性能和優化的,其中提供了很好的建議。在本文中,我將在此基礎上展示我過去用來減少頁面加載時間的工具和方法。

測量 & 收集數據

性能基準測試和概要分析對於任何優化工作都是必不可少的。盲目地應用優化可能會增加代碼庫的複雜性,甚至可能使情況變得更糟。

我們需要性能數據來了解應該關注哪些部分,並驗證任何更改是否達到了預期的效果.

django-debug-toolbar

django-debug-toolbar很容易使用,並且有一個很好的界面。它可以顯示每個SQL查詢花費了多少時間,並有一個快速按鈕可以獲得該查詢的EXPLAIN輸出和其他一些有趣的細節。template-profiler是一個額外的面板,用於添加有關模板呈現過程的分析數據。

不過,django-debug-toolbar也有一些缺點。由於它集成到站點中的方式,所以只有在DEBUG = True的開發環境中使用它是有意義的。它本身也會帶來巨大的性能損失。

DTrace

DTrace就沒有這些限制。它可以用於生產服務,並提供比項目的python部分更多的細節。您可以深入瞭解數據庫、python解釋器、web服務器和操作系統,以獲得每個地方時間花費的完整信息。

這將發生在CLI中,而不是漂亮的瀏覽器UI中。DTrace腳本是用類AWK語法編寫的。在dtracetools包中還有一些有用的腳本。當您使用Joyent pkgsrc repos時,您可以使用一下代碼來安裝它:

使用DTrace和cProfile分析Django性能

這個包中有用的腳本之一是dtrace-mysql_query_monitor.d,它將顯示所有的MySQL查詢:

使用DTrace和cProfile分析Django性能

您也可以對PostgreSQL做同樣的事情:

使用DTrace和cProfile分析Django性能

輸出將會像這樣:

使用DTrace和cProfile分析Django性能

在dtracetools包中有一些非常有用的dtrace-py_*腳本,可以幫助你深入瞭解python過程本身。例如dtrace-py_cputime.d將顯示一個函數的調用次數以及包含和不包含CPU的時間:

使用DTrace和cProfile分析Django性能

在本例中,我們看到在正則表達式相關的東西上花費了一些時間,可能與URL路由有關。

cProfile

標準的python庫附帶了cProfile,它將收集函數調用的精確時間。我們可以將它與django test client一起使用來進行自動化性能測試。

儘可能多地自動化性能數據收集步驟有助於快速迭代。在最近的一個項目中,我創建了一個專用的manage.py命令來分析最重要的URL。它看起來像這樣:

使用DTrace和cProfile分析Django性能

我們不但可以打印統計數據,還可以使用pr.dump_stats(fn)將統計數據保存到磁盤。這允許我們使用flameprof進行進一步處理來創建火焰圖。

使用DTrace和cProfile分析Django性能

使用DTrace和cProfile分析Django性能

%timeit

另一個來自標準庫的方便實用程序是timeit。你會經常發現這樣的例子:

使用DTrace和cProfile分析Django性能

這在對小型語句進行實驗是很有用。

為了更進一步,我建議您安裝IPython,它會將Django manage.py shell 轉換成一個非常強大的開發環境。

除了tab-補全和其他上千個功能外,你還會擁有%timeit魔法。

使用DTrace和cProfile分析Django性能

優化

一旦你知道你的項目中哪些部分是最慢的,你就可以開始改進這些部分。通常大部分時間是花在數據庫查詢上,然後是模板呈現。

雖然每個項目可能需要不同的優化,但也有一些常見的模式。

預加載相關的對象

當您在模板中顯示一個對象列表並訪問一個相關對象的某個字段時,將觸發一個額外的數據庫查詢。這些耗時很容易地疊加起來,並導致針對一個請求的大量查詢。

當您知道您將需要哪些相關字段時,您就可以告訴Django以更有效的方式去獲得這些字段。兩個重要的方法是select_related()和prefetch_related()。

select_related()通過使用一個SQL JOIN prefetch_related()為每個查找創建一個查詢。它們易於使用,幾乎不需要對現有代碼進行任何修改,並且可以帶來巨大的改進。

索引

另一個容易應用的性能調整是確保您有正確的數據庫索引。無論您何時使用一個字段進行filter,在某些情況下是進行order_by,您都應該考慮是否需要索引。創建索引與向模型字段添加db_index = True一樣簡單,然後創建並運行結果遷移。確保使用SQL EXPLAIN去驗證改進。

緩存

緩存是一個很大的主題,有很多方法可以通過緩存來提高django的性能。根據環境和性能特徵,使用緩存的位置、持續時間和層將不同。

django cache 框架是在各個層上利用Memcached的一種簡單方法。@cached_property裝飾器通常對胖模型方法很有幫助。

預計算

有些計算對於一個HTTP請求的時間預算來說太長了。在這些情況下,我發現在後臺進程中預先計算所需的數據是很有用的。這可以通過像Celery這樣的任務隊列來完成,也可以通過一個manage.py命令來降低複雜性,該命令可以作為一個服務或作為一個定時任務長期運行。

Django之外

除了這些常見的情況外,還有許多進一步優化web項目的方法。通過反規範化更改數據庫模式可能會改進一些查詢。其他技術將在很大程度上取決於項目的環境。

通常也有很多機會去優化堆棧和下面的東西。從瀏覽器中測量性能數據,在Django中花費的時間只會越來越少。有了這些新數據,你就可以開始處理DOM渲染、CSS和JS,減少圖像請求大小或更好的網絡路由。

查看更低級別的改進也能帶來巨大的好處。即使是低級別的小改進也會導致性能的提高,這僅僅是因為這些部分運行得太頻繁了。

最近的一個例子是,通過改變python解釋器的編譯方式,每個請求獲得了約120ms的提升。我測試的cpython版本中已經啟用了retpoline緩解。這是一個孤立的內部服務,而威脅模型是不需要這個服務的。因此,僅通過不使用-mindirect-branch=thunk-inline -mfunction-return=thunk-inline -mindirect-branch-register進行編譯就可以極大地提高性能。

英文原文:https://wiedi.frubar.net/blog/2019/11/18/django-performance/
譯者:Nothing


分享到:


相關文章: