PHP 性能分析
什么是性能分析?
性能分析是衡量應(yīng)用程序在代碼級別的相對性能。性能分析將捕捉的事件包括:CPU的使用,內(nèi)存的使用,函數(shù)的調(diào)用時長和次數(shù),以及調(diào)用圖。性能分析的行為也會影響應(yīng)用性能。
影響的程度取決于基準測試。基準測試在外部執(zhí)行,用于衡量應(yīng)用真實性能。所謂真實性能,即終端用戶所體驗的應(yīng)用表現(xiàn)。
什么時候應(yīng)該進行性能分析?
在考慮是否進行性能分析時,你首先要想:應(yīng)用是否存在性能問題?如果有,你要進一步考慮:這個問題有多大?
如果你不這樣做,將會陷入一個陷阱——過早優(yōu)化,這可能會浪費你的時間。
為了評斷應(yīng)用是否存在性能問題,你應(yīng)該確定性能目標。例如,100 個并發(fā)用戶的響應(yīng)時間小于 1s 。然后,你需要進行基準測試,看是否達到這個目標。一個常見的錯誤是,在開發(fā)環(huán)境進行基準測試。事實上,你必須在生產(chǎn)環(huán)境進行基準測試。(實際生產(chǎn)環(huán)境或模擬的生產(chǎn)環(huán)境,后者很容易在 SaaS 實現(xiàn)(例如:OneAPM PHP 應(yīng)用性能在線分析示例)。
用于基準測試的產(chǎn)品很多,包括 ab,siege 和 JMeter。我個人比較喜歡 JMeter 的功能集,但 ab 和 siege 更加易用。
一旦你確定應(yīng)用存在性能問題,就需要分析其性能,實施改進,然后再一次進行基準測試,查看問題是否解決。每一次變更之后,你都該進行基準測試查看效果。如果你做了很多變更,卻發(fā)現(xiàn)應(yīng)用性能有所下降,你就無法確定具體是哪一次變更導致了這個問題。
下圖是我定義的性能生命周期:
性能下降的一般原因
導致性能下降的一般原因中,有些相當出人意料。即便是像 PHP 這樣的高級語言,代碼的好壞也很少是問題的根源。在當今的硬件配置條件下,CPU 很少是性能限制的原因。常見的原因反而是:
數(shù)據(jù)存儲
- PostgreSQL
- MySQL
- Oracle
- MSSQL
- MongoDB
- Riak
- Cassandra
- Memcache
- CouchDB
- Redis
外部資源
- APIs
- 文件系統(tǒng)
- 網(wǎng)絡(luò)接口
- 外部流程
糟糕的代碼
選擇哪一種性能分析器?
在 PHP 世界里,有兩個截然不同的的性能分析器——主動和被動。
主動 VS 被動性能分析
主動分析器在開發(fā)過程中使用,由開發(fā)人員啟用。主動分析器收集的信息比被動分析器多,對性能的影響更大。通常,主動分析器不能用在生產(chǎn)環(huán)境中。XDebug 就是一種主動分析器。
因為無法在生產(chǎn)環(huán)境中使用主動分析器,Facebook 推出了一個被動分析器——XHProf。XHProf 是為了在生產(chǎn)環(huán)境中使用而打造的。它對性能的影響最小,同時收集足夠的信息用于診斷性能問題。XHProf 和 OneAPM 都是被動分析器。
通常,XDebug 收集的額外信息對于一般的性能問題分析并不必要。這意味著,被動分析器是用于不間斷性能分析的更佳選擇,即使是在開發(fā)環(huán)境中。
XHProf + XHGui
XHProf 由 Facebook 開發(fā)的,包含一個基本的用戶界面用于查看性能數(shù)據(jù)。此外,Paul Reinheimer 開發(fā)了 XHGui 和一個增強的用戶界面(UI)用于查看、比較和分析性能數(shù)據(jù)。
安裝
安裝 XHProf
XHProf 可通過 PECL 安裝,步驟如下:
$ pecl install xhprof-beta該 pecl 命令將嘗試自動更新你的 php.ini 設(shè)置。pecl 嘗試更新的文件可以使用以下命令找到:
$ pecl config-get php_ini它會在指定的文件(如果有的話)頂部增加新的配置行。你可能想把他們移到一個更合適的位置。
一旦你編譯了該擴展程序,您必須啟用它。為此,您需要在 PHP INI 文件添加以下代碼:
[xhprof] extension=xhprof.so之后,結(jié)合 XHGui 就能輕松地執(zhí)行性能分析與檢查。
安裝 XHGui
安裝 XHGui,必須直接從 git 獲取。該項目可以在 github 上找到,地址為https://github.com/perftools/xhgui
XHGui 要求:
- PHP 5.3+
- ext/mongo
- composer
- MongoDB (若只需要收集數(shù)據(jù),則可選可不選;若需要數(shù)據(jù)分析,則為必選)
首先,克隆項目到任意位置。在基于 Debian 的 Linux 系統(tǒng)(例如 Ubuntu 等等),可能是 /var/www。在 Mac OS X 系統(tǒng),可能是 /Library/WebServer/Documents。
$ cd /var/www $ git clone https://github.com/perftools/xhgui.git $ cd xhgui $ php install.php最后一個命令是運行 composer 以安裝依賴并檢查 XHGui 緩存目錄的權(quán)限。如果失敗,你可以手動運行 composer install。
下一步,你可能需要創(chuàng)建配置文件。這一步很容易實現(xiàn),可以使用在 /path/to/XHGui/config/config.default.php 下的默認配置文件。
如果你在本地運行 MongoDB,沒有身份驗證,則可能不需要這樣做。因為它將回退為默認值。而在多服務(wù)器環(huán)境中,你會需要一個所有服務(wù)器都能進行存儲的遠程 MongoDB 服務(wù)器,并進行恰當?shù)呐渲谩?/p>
為提高 MongoDB 的性能,你可以運行以下指令以添加索引:
$ mongo> use xhprof db.results.ensureIndex( { 'meta.SERVER.REQUEST_TIME' : -1 } ) db.results.ensureIndex( { 'profile.main().wt' : -1 } ) db.results.ensureIndex( { 'profile.main().mu' : -1 } ) db.results.ensureIndex( { 'profile.main().cpu' : -1 } ) db.results.ensureIndex( { 'meta.url' : 1 } )其他配置
如果你不想在生產(chǎn)環(huán)境中安裝 mongo ,或無法讓 Web 服務(wù)器訪問 mongo 服務(wù)器,您可以將性能分析數(shù)據(jù)保存在磁盤中,再導入到本地 MongoDB 供以后分析。
為此,請在 config.php 中進行以下修改:
<?php 'save.handler' = 'file', 'save.handler.filename' => '/path/to/xhgui/xhprof-' .uniqid("", true). '.dat', ?>改變文件中的 save.handler,然后取消批注 save.handler.filename ,為其賦一個恰當?shù)闹怠?/p>
注意:默認每天只保存一個分析文件。
一旦分析數(shù)據(jù)的準備就緒,你就可以使用 XHGui 附帶的腳本導入之:
$ php /path/to/xhgui/external/import.php /path/to/file.dat在此之后的步驟都相同。
運行 XHGui
XHGui 是以 PHP 為基礎(chǔ)的 Web 應(yīng)用程序,你可以以 /path/to/xhgui/webroot 為根文件,設(shè)置一個標準的虛擬主機。
或者,你可以簡單地使用?PHP 5.4+ cli-server?例如:
$ cd /path/to/xhgui $ php -S 0:8080 -t webroot/這將使 XHGui 在所有網(wǎng)絡(luò)接口都可通過 8080 端口進行通信。
運行性能分析器
運行分析器時,你需要在待分析的所有頁面包含 external/header.php 腳本。為此,你可以在 PHP ini 文件設(shè)置 auto_prepend_file 。你既可以直接在公共 INI 文件進行設(shè)置,也可以限制到單一的虛擬主機。
對于 Apache 服務(wù)器,添加以下代碼:
php_admin_value auto_prepend_file "/path/to/xhgui/external/header.php"對于 Nginx 服務(wù)器,在服務(wù)器配置中添加以下代碼:
fastcgi_param PHP_VALUE "auto_prepend_file=/path/to/xhgui/external/header.php";如果您使用 PHP 5.4+ cli-server(PHP -S),則必須通過命令行標記進行設(shè)置:
$ php -S 0:8080 -dauto_prepend_file=/path/to/xhgui/external/header.php默認情況下,分析器運行時只分析(大約) 1% 的請求。這是由以下?external/header.php?代碼控制的:
<?php if (rand(0, 100) !== 42) { return; } ?>如果你想分析每一個請求(例如,在開發(fā)階段),你可以將這段代碼注釋掉。如果你想讓分析 10% 的請求,你可以做如下改動:
<?php if (rand(0, 10) !== 4) {return; } ?>這允許你對一小部分用戶請求進行分析,而不過多影響單個用戶或太多用戶。
如果你想在性能分析時進行手動控制,你可以這樣做:
<?php if (!isset($_REQUEST['A9v3XUsnKX3aEiNsUDZzV']) && !isset($_COOKIE['A9v3XUsnKX3aEiNsUDZzV'])) { return; } else {// Remove trace of the special variable from REQUEST_URI$_SERVER['REQUEST_URI'] = str_replace(array('?A9v3XUsnKX3aEiNsUDZzV', '&A9v3XUsnKX3aEiNsUDZzV'), '', $_SERVER['REQUEST_URI']);setcookie('A9v3XUsnKX3aEiNsUDZzV', 1); }if (isset($_REQUEST['no-A9v3XUsnKX3aEiNsUDZzV'])) { setcookie('A9v3XUsnKX3aEiNsUDZzV', 0, time() - 86400);return; } ?>這段代碼會檢查一個隨機命名的 GET/POST/COOKIE 變量(在此例中為:A9v3XUsnKX3aEiNsUDZzV),同時創(chuàng)建一個同名的 Cookie ,用于分析該請求的整個過程,例如:表單提交后的重定向,Ajax 請求等等。
此外,它允許一個名為 no-A9v3XUsnKX3aEiNsUDZzV 的 GET/POST 變量來刪除 Cookie ,停止分析。
當然,我們歡迎大家嘗試使用 OneAPM 來為您的?PHP?和?Java?應(yīng)用做免費的性能分析。OneAPM 獨有的探針能夠深入到所有 PHP 和 Java 應(yīng)用內(nèi)部完成應(yīng)用性能管理和監(jiān)控,包括代碼級別性能問題的可見性、性能瓶頸的快速識別與追溯、真實用戶體驗監(jiān)控、服務(wù)器監(jiān)控和端到端的應(yīng)用性能管理。 OneAPM 可以追溯到性能表現(xiàn)差的 SQL 語句 Traces 記錄、性能表現(xiàn)差的第三方 API、Web 服務(wù)、Cache 等等。
使用 XHGui
XHGui 提供了許多協(xié)助性能評估的功能,既適用于單次運行,也能滿足聚合環(huán)境——讓你精確至具體問題、發(fā)現(xiàn)趨勢。
術(shù)語
為了提高 XHGui 的使用效率,你需要熟悉許多術(shù)語:
1.調(diào)用次數(shù)
函數(shù)調(diào)用的次數(shù)
2.[包含] 實際執(zhí)行時間 (wt)
函數(shù)實際執(zhí)行時間
3.[包含] CPU 使用/CPU 用時 (cpu)
運行該函數(shù) CPU 所用時間
4.[包含] 內(nèi)存使用 (mu)
目前該函數(shù)使用的內(nèi)存量
5.[包含] 內(nèi)存使用量峰值 (pmu)
函數(shù)使用的內(nèi)存高峰
6.專一實際執(zhí)行時間 (ewt)
7.專一 CPU 時間 (ecpu)
8.專一內(nèi)存使用量 (emu)
9.專一內(nèi)存使用量峰值 (epmu)
術(shù)語 2 至 5 都是包含型的測量指標(盡管不總是明確指出),這些指標會計算函數(shù)及其子函數(shù)的調(diào)用。術(shù)語 6 至 9 是專一型的測量指標——它們只計算函數(shù)本身的資源調(diào)用。所有的測量數(shù)值都是調(diào)用該函數(shù)后的累計值。(例如,如果一個函數(shù)調(diào)用兩次,第一次用時 900 毫秒,第二次,因為緩存的緣故,只耗時 40 毫秒,最終顯示的時間就是 940 毫秒)。
準備開始
一旦在 HTTP 服務(wù)器上運行 XHGui ,你首先會看到:
在頂部,你會看到一個菜單,它包含:
Recent — 近期大部分運行 (分頁)
Longest wall time — 根據(jù)實際執(zhí)行時間從最慢的運行開始排序
Most CPU — 從占用 CPU 時間最多的運行開始排序
Most Memory — 從占用內(nèi)存最多的運行開始排序
Custom View — 執(zhí)行 MongoDB 自定義查詢
Watch Functions — 應(yīng)該出現(xiàn)在審查頁面頂部的標記函數(shù)
Waterfall — 從實驗性視圖查看并發(fā)請求的相互影響
在本教程中,我選擇分析用 WordPress 搭建的網(wǎng)站性能。互聯(lián)網(wǎng)上多于18% 的網(wǎng)站都是基于 WordPress 搭建的,這意味著,即便是對 WordPress 很小的性能改進, 亦能產(chǎn)生巨大影響。
查看一次運行的性能數(shù)據(jù)
分析了幾個頁面的性能(或?qū)肓宋募?之后,你會看到它們羅列在 XHGui :
查看一次運行的性能數(shù)據(jù),只需點擊日期。
通過單擊適當?shù)谋眍^,你可以根據(jù)實際執(zhí)行時間 (wt) , CPU 時間 (CPU) 、內(nèi)存使用量 (mu) 或 內(nèi)存使用量峰值 (pmu) 查看這些運行情況。從而輕易找出最慢的頁面。
單個性能頁面展示了相當多的信息。在左側(cè)可以看到運行的總體情況,以及運行時的環(huán)境數(shù)據(jù),包括 GET (或 POST) 數(shù)據(jù)和服務(wù)器數(shù)據(jù):
在右側(cè),展示了 watch function 列表:
該表詳細列出了函數(shù)名稱,調(diào)用次數(shù) ,專一實際執(zhí)行時間 (ewt), 專一內(nèi)存使用量(emu)、和專一內(nèi)存使用峰值(epmu)。此外,你可能會注意到頁面頂部的兩個按鈕, “View Callgraph(查看調(diào)用圖)” 和 “Compare this run(對比此次運行)”。
接下來,我們看到兩個圖。圖一展示了專一實際執(zhí)行時間最長的六個函數(shù),該時間是用在函數(shù)本身的時間(不包含任何子函數(shù)調(diào)用所占的時間)。圖二展示內(nèi)存使用量最大的六個函數(shù)。這些圖通常能將你指向性能瓶頸。
函數(shù)的細節(jié)在下方列出。如果將鼠標滑過圖中的圓柱,這些信息也將出現(xiàn)在提示框中。
最后,我們看到性能分析器收集到的大宗信息——函數(shù)列表:
該表包含一個浮動的標題欄(即便鼠標向下滾動,該欄目也會保持在屏幕頂端),包含函數(shù)名,調(diào)用次數(shù),和前面提到的專一和包含的測量值。
默認情況下,該表按專一實際執(zhí)行時間排序,時間最長者排在首位。通常你不會想改變這一次序,因為這讓你快速找出運行最慢的函數(shù),除非你想看內(nèi)存使用量。
當你想查看一個函數(shù)的運行情況時,點擊該函數(shù),會跳轉(zhuǎn)到其詳細頁面。該頁面首先會遞歸展示函數(shù)本身的細節(jié)。接下來, “Parent Functions(父函數(shù))” 部分列出所有直接調(diào)用該函數(shù)的函數(shù)。最后,“Child Functions(子函數(shù))”列出該函數(shù)直接調(diào)用的其他函數(shù)。
父函數(shù)按照專一實際運行時間,列出標準列表數(shù)據(jù)。
你需要確定:是函數(shù)本身運行緩慢,還是調(diào)用它的次數(shù)太多導致累積的實際執(zhí)行時間太長。通過檢查該函數(shù)的調(diào)用計數(shù),然后回顧其父函數(shù)列表。
如果你覺得函數(shù)調(diào)用次數(shù)沒問題,你就要看看子函數(shù)運行情況。此處才是函數(shù)運行消耗時間的部分。
子函數(shù)只顯示包含測量值;這是因為你想很快找到耗時最長的代碼路徑。
你可以點擊每個子函數(shù),下鉆到相同的細節(jié)視圖,并進行相同的分析。
比較性能數(shù)據(jù)
XHGui 最好的特性在于比較兩個不同的運行。這使你:
- 修改系統(tǒng) (如啟用 OpCache, MySQL 查詢緩存) 并比較結(jié)果
- 修改代碼(代碼或 SQL 優(yōu)化)并比較結(jié)果
- 將異常的運行與“正常”運行比較
比較兩個運行時,你必須首先選擇一個基礎(chǔ)運行。點擊其日期就能看到該運行的詳細信息頁。
接下來,單擊右上角的 “Compare this run” 按鈕:
接著會跳轉(zhuǎn)到同一 URL 下的運行列表,你可以選擇一個進行比較:
點擊你想進行比較的運行的 "Compare" 按鈕,將跳轉(zhuǎn)到比較頁面。
比較視圖只顯示兩個運行之間的差異。在頁面頂部顯示比較中的兩個運行,以及一些輔助修改排序的按鈕。
接下來是概覽:
盡管這個表的所有信息都有用,但特別值得注意的兩個差別是 "函數(shù)調(diào)用次數(shù)" 和 "專一實際運行時間" 。
函數(shù)調(diào)用次數(shù)的差別暗示著兩次運行的重要差異:不同的代碼路徑或緩存。第一個差別可能是有意的優(yōu)化導致的,但若這并非你的目的,比較這兩個運行很可能不會有太大的價值。另一方面,緩存是有益且有效的提高性能的方式。這種比較很容易驗證緩存是否發(fā)生。
包含實際執(zhí)行時間的百分比差展示了性能調(diào)優(yōu)的實際成果。理想情況下,我們將看到一個較小的百分比——這是第二運行時間比上第一次運行時間的占比。在截圖中,第二次運行只花了第一次運行 79% 的時間,這意味著性能提升了 21%。
最后,我們看到功能細節(jié):
請記住,該視圖只展示差別。差別通過綠色的負數(shù)和紅色的正數(shù)表示。(負數(shù)表明調(diào)用次數(shù)更少,實際執(zhí)行時間更短,CPU 耗時更短或內(nèi)存消耗更少)如果沒有差異,則顯示為灰色的 0。
與其他表一樣,您可以在任意列進行排序,默認的順序方式是函數(shù)的調(diào)用次序。
在這里你可以驗證,你做的改變是否確有效果,是否為預(yù)期效果。你也可以在性能下降時使用該視圖追蹤原因。
性能提高的一個好例子是:基于一個條件只調(diào)用一個函數(shù)——例如,您可能不需要過濾數(shù)據(jù),如果之前已經(jīng)做了。
當你做出這種改變時,你會預(yù)期過濾函數(shù)的調(diào)用次數(shù)減少,從而性能提升。
這兩件事都可以在此處得到驗證,以及其他意想不到的原因——你的條件比過濾本身需要更長的時間?如果真是如此,這將對性能產(chǎn)生負面影響。
在此處,我們可以看到, NOOP_Translations::translate 和 apply_filter 的調(diào)用次數(shù)都減少了,但是 apply_filter 的專一內(nèi)存使用量增加了 133560 個字節(jié)!
發(fā)現(xiàn)趨勢
對我來說,XHGui 最強大的功能是查看趨勢。因為 XHProf 是被動分析器,可以在所有環(huán)境中啟用 (dev、qa、階段性、生產(chǎn)),可以持續(xù)地對流量取樣分析。
審查給定 URL 的所有數(shù)據(jù),只需在運行列表點擊它:
這將跳轉(zhuǎn)到該 URL 運行頁面。
該頁面顯示兩個重要圖表。第一個顯示實際運行時間和 CPU 時間,第二個顯示內(nèi)存使用情況和峰值內(nèi)存使用。這些圖表列表中運行的數(shù)據(jù),包括每次運行的 URL,時間,實際運行時間、CPU 時間、內(nèi)存使用和峰值。
這些圖是查看趨勢和異常值的關(guān)鍵所在。但是該如何處理這些信息呢?
對于數(shù)據(jù)異常者,首先你可以將鼠標懸浮在它上面驗明正身,接著,你可以看一下它的單次運行。或用其他正常運行與其比較,從而發(fā)現(xiàn)不同。
對于趨勢,最好的選擇是審查趨勢開始的時間——你在此時添加緩存了嗎?隨著緩存變得更加完整,整體趨勢應(yīng)該向下。或者你的緩存失效,你將看到一個上升趨勢,此時緩存正在重建。
默認情況下,這些圖表顯示最近 100 次運行,你可以點擊下一頁去查看更久遠時間的運行。
另外,你可以點擊搜索按鈕來定制顯示的界面:
單擊該按鈕將顯示搜索表單:
你可以搜索具體日期之間的運行。也可以查看最近 30分鐘、1小時、2小時、12小時、24小時、1周、2周或 30天內(nèi) 的運行——更小的時間間隔適合評估性能調(diào)優(yōu)的結(jié)果。
最后,你可以使用 PHPs DateTimeIntervalInterval?規(guī)范格式指定自定義時間區(qū)間——例如,最近 2天 可使用 P2D,最近 15分鐘 可使用 PT15M。
Watch Functions
Watch functions 允許你通過正則表達式識別特定的函數(shù),或函數(shù)組,并顯示在單個運行頁面(見前文)。
因為可以使用正則表達式,我們可以輕易地查看一個模塊或擴展中的功能。
例如,查看所有 MySQL 活動,只需添加如下列表的任意一項:
- mysql_(.*)for ext/mysql
- mysqli(.*)for ext/mysqli
- pdo(.*)for PDO (適用于所有 PDO-based 數(shù)據(jù)庫交互)
如果你使用諸如 Propel 的 ORM,你可能使用?(.*)Query::(.*)?追蹤所有 Query 類。
調(diào)用圖(Callgraphs)
XHProf 的最后一部分是調(diào)用圖 ,該圖展示運行的代碼執(zhí)行路徑。
點擊單一運行頁面頂部的“View Callgraph”按鈕即可查看調(diào)用圖。
在調(diào)用圖中,拖拽結(jié)點可以更好地查看數(shù)據(jù)。鼠標懸浮在每個點擊上,會顯示其包含實際執(zhí)行時間,同時允許你進入該函數(shù)的詳情頁。
性能調(diào)優(yōu)
不用運行的代碼才是絕好的代碼。其他只是好的代碼。所以,性能調(diào)優(yōu)時,最好的選擇是首先確保運行盡可能少的代碼。
OpCode 緩存
首先,最快且最簡單的選擇是啟用 OpCode 緩存。OpCode 緩存的更多信息可以在?這里?找到。
在上圖,我們看到啟用 Zend OpCache 后發(fā)生的情況。最后一行是我們的基準,也即沒有啟用緩存的情況。
在中間行,我們看到較小的性能提升,以及內(nèi)存使用量的大幅減少。小的性能提升(很可能)來自 Zend OpCache 優(yōu)化,而非 OpCode 緩存。
第一行是優(yōu)化和 OpCode 緩存后結(jié)果,我們看到很大的性能提升。
現(xiàn)在,我們看看 APC 之前和之后的變化。如上圖所示,跟 Zend OpCache 相比,隨著緩存的建立,我們看到初始(中間行)請求的性能下降,在消耗時長與內(nèi)存使用量方面的表現(xiàn)都明顯下降。
接著,隨之 opcode 緩存的建立,我們看到類似的性能提升。
內(nèi)容緩存
第二件我們能做的事是緩存內(nèi)容——這對 WordPress 而言小菜一碟。它提供了許多安裝簡便的插件來實現(xiàn)內(nèi)容緩存,包括 WP Super Cache。WP Super Cache 會創(chuàng)建網(wǎng)站的靜態(tài)版本。該版本會在出現(xiàn)諸如評論事件時依照網(wǎng)站設(shè)置自動過期。(例如,在非常高負載情況下,您可能會想禁止任何原因造成的緩存過期)。
內(nèi)容緩存只能在幾乎沒有寫操作時有效運行,寫操作會使緩存失效,而讀操作不會。
你也應(yīng)該緩存應(yīng)用從第三方 API 處收到的內(nèi)容,從而減少由于 API 可用性導致的延遲與依賴。 ? WordPress 有兩個緩存插件,可以大大提高網(wǎng)站的性能:?W3 Total Cache?和?WP Super Cache。
這兩個插件都會創(chuàng)建網(wǎng)站的靜態(tài) HTML 副本,而不是每次收到請求時再生成頁面,從而壓縮響應(yīng)時間。
如果你正在開發(fā)自己的應(yīng)用程序,大多數(shù)框架都有緩存模塊:?
- Zend Framework 2:Zend\Cache
- Symfony 2:Multiple options
- Laravel 4:Laravel Cache
- ThinkPHP 3.2.3:ThinkPHP??Cache
查詢緩存
另一個緩存選項是查詢緩存。針對 MySQL,有一個通用的查詢緩存幫助極大。對于其他數(shù)據(jù)庫,將查詢結(jié)果集緩存在 Memcached 或者 cassandra 這樣的內(nèi)存緩存,也非常有效。
跟內(nèi)容緩存一樣,查詢緩存在包含大量讀取操作的場景是最有效的。由于少量的數(shù)據(jù)改動就會使大塊的緩存區(qū)無效,尤其不能在這種情況下依賴 MySQL 查詢緩存來提高性能。
查詢緩存或許在生成內(nèi)容緩存時對性能有提升。
如下圖所示,當我們開啟查詢緩存后,實際運行時間減少了 40% ,盡管內(nèi)存使用量沒有明顯改變。
現(xiàn)有三種類型的緩存選項,由?query_cache_type?控制設(shè)置。
- 設(shè)置值為?0?或?OFF?將禁用緩存
- 設(shè)置值為?1?或?ON?將緩存除了以?SELECT SQL_NO_CACHE?開頭之外的所有選擇
- 設(shè)置值為?2?或?DEMAND?只會緩存以?SELECT SQL_CACHE?開頭的選擇
此外,你應(yīng)該將?query_cache_size?設(shè)置為非零值。將它設(shè)置為零將禁用緩存,不管query_cache_type?是否設(shè)置。
想得到設(shè)置緩存的幫助,與許多其他性能相關(guān)的設(shè)置,請查看?mysql-tuning-primer?腳本。
MySQL 查詢緩存的主要問題是,它是全局的。對緩存結(jié)果集構(gòu)成的表格的任何更改都將導致緩存失效。在寫入操作頻繁的應(yīng)用程序中,這將使緩存幾乎無效。
然而,你還有許多其他選擇,可以根據(jù)你的需求和數(shù)據(jù)集建立更多的智能緩存,例如?Memcached?,?riak?,?cassandra?或?redis
查詢優(yōu)化
如前所述,數(shù)據(jù)庫查詢常常是程序執(zhí)行緩慢的原因,查詢優(yōu)化往往能比代碼優(yōu)化帶來更多切身的好處。
查詢優(yōu)化有助于生成內(nèi)容緩存時提高性能,而且,在無法緩存這種最壞的情況下也有益處。
除了分析, MySQL 還有一個幫助識別慢查詢的選擇——慢查詢?nèi)罩尽B樵內(nèi)罩緯涗浰泻臅r超過指定時間的查詢,以及不使用索引的查詢(后者為可選項)。
您可以在?my.cnf?中使用以下配置啟用日志。
[mysqld] log_slow_queries =/var/log/mysql/mysql-slow.log? long_query_time =1 log-queries-not-using-indexes任何查詢?nèi)绻?long_query_time?(以秒為單位),該查詢就會記錄到日志文件log_slow_queries?中。默認值是10秒,最低1秒。
此外,?log-queries-not-using-indexes?選項可以將任何不使用索引的查詢捕獲到日志中。
之后我們可以用與 MySQL 捆綁在一起的?mysqldumpslow?命令檢查日志。
在 WordPress 安裝時使用這些選項 ,主頁加載完成并運行后得到如下數(shù)據(jù):?
$ mysqldumpslow -g "wp_" /var/log/mysql/mysql-slow.logReading mysql slow query log from /var/log/mysql/mysql-slow.logCount: 1??Time=0.00s (0s) Lock=0.00s (0s) Rows=358.0(358), user[user]@[host] SELECT option\_name, option\_value FROM wp_options WHERE autoload ='S'Count: 1 Time=0.00s (0s) Lock=0.00s (0s) Rows=41.0(41), user[user]@[host] SELECT user\_id, meta_key, meta_value FROM wp_usermeta WHERE user_id IN (N)首先,注意所有字符串值都以?S?表示,數(shù)字則以?N?表示。你可以添加?-a?標志來顯示這些值。?
接下來,請注意,這兩個查詢均耗時 0.00 s,這意味著他們的耗時在 1 秒的閾值以下,且沒有使用索引。
在 MySQL 控制臺 使用 EXPLAIN,可以檢查性能下降的原因:
????mysql> EXPLAIN SELECT option_name, option_value FROM wp_options WHERE autoload = 'S'\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: wp_optionstype: ALLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 433Extra: Using where此處,我們看到?possible_keys?是 NULL,從而確認未使用索引。
EXPLAIN?是對優(yōu)化 MySQL 查詢非常強大的工具,更多信息可以在?這里?找到。
PostgreSQL 同樣也包括一個 EXPLAIN (該 EXPLAIN 與 MySQL 的差別很大),而 MongoDB 有$explain 元 操作符。
代碼優(yōu)化
通常只有當你不再受到 PHP 本身限制(通過使用 OpCode 緩存),緩存了盡可能多的內(nèi)容,優(yōu)化了查詢之后,才可以開始調(diào)整代碼。
代碼和查詢優(yōu)化帶來足夠的性能提升才能創(chuàng)建其他緩存;代碼在最糟糕的環(huán)境(沒有緩存)下性能越高,應(yīng)用就越穩(wěn)定,重建緩存的速度也就越快。
讓我們看看如何(潛在地)優(yōu)化我們的 WordPress 安裝。
首先,讓我們看看最慢的函數(shù):?
令我驚訝的是,列表中的第一項?不是?MySQL (事實上?mysql_query()?是第四),而是apply_filter()?函數(shù)。
WordPress 代碼庫的特點是,通過基于事件的過濾系統(tǒng)執(zhí)行多種數(shù)據(jù)轉(zhuǎn)換,執(zhí)行次序按照數(shù)據(jù)經(jīng)內(nèi)核、插件添加或回調(diào)的順序。
apply_filter()?函數(shù)是這些回調(diào)應(yīng)用的地方。
首先,你可能會注意到,函數(shù)被調(diào)用 4194 次。如果我們點擊查看更多細節(jié),就可以按照“調(diào)用次數(shù)”降序排列“父函數(shù)”,從而發(fā)現(xiàn)?translate()?調(diào)用了apply_filter()?函數(shù) 778 次。
這很有趣,因為實際上我不使用任何翻譯。我(并懷疑大多數(shù)用戶)在使用 WordPress 軟件時都設(shè)置為本土語言:英語。
因此,讓我們點擊查看細節(jié),進一步查看該?translate()?函數(shù)在做什么。
在這里,我們看到兩間有趣的事。首先,在父函數(shù)中,有一個被調(diào)用了773次:__()。
查看該函數(shù)的源代碼后,我們發(fā)現(xiàn)它是?translate()?的包裝器。
????<?php/***?Retrieves the translation of $text. If there is no translation, or*?the domain isn't loaded, the original text is returned.**?@see translate() An alias of translate()*?@since 2.1.0**?@param string $text Text to translate*?@param string $domain Optional. Domain to retrieve the translated text*?@return string Translated text*/function __( $text, $domain = 'default' ) {return translate( $text, $domain );}?>根據(jù)經(jīng)驗法則,函數(shù)調(diào)用代價昂貴,應(yīng)該盡量避免。現(xiàn)在我們總是調(diào)用 __() 而不是translate()?,我們應(yīng)該把別名改為?translate()?來保持向后兼容性,而 __() 則不再調(diào)用非必要的函數(shù)。
然而,實際上,這種改變不會帶來多大的差異,只是微觀的優(yōu)化罷了——但它的確提高了代碼可讀性,簡化了調(diào)用圖。
繼續(xù)前進,讓我們看看子函數(shù):
現(xiàn)在,深入該函數(shù),我們看到有 3 個 函數(shù)或方法被調(diào)用,每個 778 次:
- get_translations_for_domain()
- NOOP_Translations::translate()
- apply_filters()
按照包容性實際運行時間降序排列,我們看到?apply_filter()?是目前為止耗時最長的調(diào)用。
查看代碼:
????<?phpfunction translate( $text, $domain = 'default' ) {$translations = get_translations_for_domain( $domain );return apply_filters( 'gettext', $translations->translate( $text ), $text, $domain );}?>這段代碼的作用是檢索一個翻譯對象,然后將 $translations->translate() 的結(jié)果傳給apply_filter()?。我們發(fā)現(xiàn) $translations 是?NOOP_Translations?類的一個實例。
僅根據(jù)名稱(NOOP),再經(jīng)代碼中的注釋證實,我們發(fā)現(xiàn)翻譯器實際上沒有任何動作!
????<?php/***?Provides the same interface as Translations, but doesn't do anything*/class NOOP_Translations {?>因此,也許我們完全可以避免這種代碼!
通過在代碼上進行小規(guī)模調(diào)試,我們看到當前使用的是默認的域,我們可以修改代碼以忽略翻譯器:
????<?phpfunction translate( $text, $domain = 'default' ) {if ($domain == 'default') {return apply_filters( 'gettext', $text, $text, $domain );}$translations = get_translations_for_domain( $domain );return apply_filters( 'gettext', $translations->translate( $text ), $text, $domain );}?>接下來,我們再次分析,確保要運行至少兩次——確保所有緩存都建立,才是公平的對比!
這次運行的確更快!但是,快多少?為什么?
使用 XHGui 的比較運行這一特性就能找到答案。回到我們最初的運行,點擊右上角的 “比較此處運行” 按鈕,并從列表中選擇新的運行。
我們發(fā)現(xiàn),函數(shù)調(diào)用的次數(shù)減少了3% ,包容性實際運行時間減少 9% ,包容性CPU時間減少12%!?
之后,可以按調(diào)用次數(shù)降序排列細節(jié)頁,這證實(如同我們的預(yù)期)get_translations_for_domain()?和?NOOP_Translations::translate()?函數(shù)的調(diào)用次數(shù)減少。同樣,可以確認沒有預(yù)料之外的變化發(fā)生。
30 分鐘的工作帶來9 - 12% 的性能提升,這非常可喜。這就意味著真實世界的性能收益,即便是在應(yīng)用了 opcache 之后。
現(xiàn)在我們可以對其函數(shù)重復(fù)這個過程,直到找不到更多優(yōu)化點。
注意:此更改已提交到 WordPress.org 并已獲更新。你可以在?WordPress Bug Tracker跟蹤討論,查看實踐過程。此更新計劃包含在 WordPress 4.1 版本中。
其他工具
除了出色的 XHProf/XHGui,還有一些很好的工具。
New Relic & OneAPM
New Relic?與?OneAPM? 均提供前后端性能分析;洞察后臺堆棧訊息,包括 SQL 查詢與代碼分析,前端 DOM 與 CSS 呈現(xiàn),以及 Javascript 語句。OneAPM?更多功能請移步 (OneAPM 在線DEMO)?
uprofiler
uprofiler?是目前還未發(fā)布的 Facebook XHProf 分支,該分支計劃刪除 Facebook 所需的 CLA。目前,兩者具備相同的特性,只有一些部分重命名了。
XHProf.io
XHProf.io?是 XHProf 的另一種用戶界面。XHProf.io 在配置文件存儲使用 MySQL ,用戶友好性方面不及 XHGui。
Xdebug
在 XHProf 出現(xiàn)之前,Xdebug?早已存在——Xdebug 是一種主動的性能分析器,這意味著它不應(yīng)該用于生產(chǎn)環(huán)境,但可以深入了解代碼。
然而,它必須與另一個工具配合使用以讀取分析器的輸出 , 比如?KCachegrind。但是 KCachegrind 很難安裝在非 linux 機器上。另一個選擇是?Webgrind。
Webgrind 無法提供 KCachegrind 的那些特性,但它是一個 PHP Web 應(yīng)用程序,在任何環(huán)境都易于安裝。
若搭配 KCachegrind ,你可以輕易探索并發(fā)現(xiàn)性能問題。(事實上,這是我最喜歡的剖析工具!)
結(jié)語
分析和性能調(diào)優(yōu)是非常復(fù)雜的工程。有了對的工具,并理解如何善用這些工具,我們可以很大程度地提高代碼質(zhì)量——即使是對我們不熟悉的代碼庫。
花時間去探索和學習這些工具是絕對值得的。
總結(jié)
- 上一篇: OK6410裸机开发之LED灯
- 下一篇: Visual Studio原生开发的10