PHP 效能分析第三篇: 效能調優實戰
注意:本文是我們的 PHP 效能分析系列的第三篇,點此閱讀 PHP 效能分析第一篇: XHProf & XHGui 介紹 ,或 PHP 效能分析第二篇: 深入研究 XHGui 。
在本系列的 第一篇 中,我們介紹了 XHProf 。而在 第二篇 中,我們深入研究了 XHGui UI, 現在最後一篇,讓我們把 XHProf /XHGui 的知識用到工作中!
效能調優
不用執行的程式碼才是絕好的程式碼。其他只是好的程式碼。所以,效能調優時,最好的選擇是首先確保執行儘可能少的程式碼。
OpCode 快取
首先,最快且最簡單的選擇是啟用 OpCode 快取。OpCode 快取的更多資訊可以在 這裡 找到。
在上圖,我們看到啟用 Zend OpCache 後發生的情況。最後一行是我們的基準,也即沒有啟用快取的情況。
在中間行,我們看到較小的效能提升,以及記憶體使用量的大幅減少。小的效能提升(很可能)來自 Zend OpCache 優化,而非 OpCode 快取。
第一行是優化和 OpCode 快取後結果,我們看到很大的效能提升。
現在,我們看看 APC 之前和之後的變化。如上圖所示,跟 Zend OpCache 相比,隨著快取的建立,我們看到初始(中間行)請求的效能下降,在消耗時長與記憶體使用量方面的表現都明顯下降。
接著,隨之 opcode 快取的建立,我們看到類似的效能提升。
內容快取
第二件我們能做的事是快取內容——這對 WordPress 而言小菜一碟。它提供了許多安裝簡便的外掛來實現內容快取,包括 WP Super Cache。WP Super Cache 會建立網站的靜態版本。該版本會在出現諸如評論事件時依照網站設定自動過期。(例如,在非常高負載情況下,您可能會想禁止任何原因造成的快取過期)。
內容快取只能在幾乎沒有寫操作時有效執行,寫操作會使快取失效,而讀操作不會。
你也應該快取應用從第三方 API 處收到的內容,從而減少由於 API 可用性導致的延遲與依賴。 WordPress 有兩個快取外掛,可以大大提高網站的效能: W3 Total Cache 和 WP Super Cache。
這兩個外掛都會建立網站的靜態 HTML 副本,而不是每次收到請求時再生成頁面,從而壓縮響應時間。
如果你正在開發自己的應用程式,大多數框架都有快取模組:
- Zend Framework 2:Zend\Cache
- Symfony 2:Multiple options
- Laravel 4:Laravel Cache
- ThinkPHP 3.2.3:ThinkPHP Cache
查詢快取
另一個快取選項是查詢快取。針對 MySQL,有一個通用的查詢快取幫助極大。對於其他資料庫,將查詢結果集快取在 Memcached 或者 cassandra 這樣的記憶體快取,也非常有效。
跟內容快取一樣,查詢快取在包含大量讀取操作的場景是最有效的。由於少量的資料改動就會使大塊的快取區無效,尤其不能在這種情況下依賴 MySQL 查詢快取來提高效能。
查詢快取或許在生成內容快取時對效能有提升。
如下圖所示,當我們開啟查詢快取後,實際執行時間減少了 40% ,儘管記憶體使用量沒有明顯改變。
現有三種型別的快取選項,由 query_cache_type 控制設定。
- 設定值為 0 或 OFF 將禁用快取
- 設定值為 1 或 ON 將快取除了以 SELECT SQL_NO_CACHE 開頭之外的所有選擇
- 設定值為 2 或 DEMAND 只會快取以 SELECT SQL_CACHE 開頭的選擇
此外,你應該將 query_cache_size 設定為非零值。將它設定為零將禁用快取,不管 query_cache_type 是否設定。
想得到設定快取的幫助,與許多其他效能相關的設定,請檢視 mysql-tuning-primer 指令碼。
MySQL 查詢快取的主要問題是,它是全域性的。對快取結果集構成的表格的任何更改都將導致快取失效。在寫入操作頻繁的應用程式中,這將使快取幾乎無效。
然而,你還有許多其他選擇,可以根據你的需求和資料集建立更多的智慧快取,例如 Memcached , riak , cassandra 或 redis
查詢優化
如前所述,資料庫查詢常常是程式執行緩慢的原因,查詢優化往往能比程式碼優化帶來更多切身的好處。
查詢優化有助於生成內容快取時提高效能,而且,在無法快取這種最壞的情況下也有益處。
除了分析, MySQL 還有一個幫助識別慢查詢的選擇——慢查詢日誌。慢查詢日誌會記錄所有耗時超過指定時間的查詢,以及不使用索引的查詢(後者為可選項)。
您可以在 my.cnf 中使用以下配置啟用日誌。
[mysqld]
log_slow_queries =/var/log/mysql/mysql-slow.log
long_query_time =1
log-queries-not-using-indexes
任何查詢如果慢於 long_query_time (以秒為單位),該查詢就會記錄到日誌檔案 log_slow_queries 中。預設值是10秒,最低1秒。
此外, log-queries-not-using-indexes 選項可以將任何不使用索引的查詢捕獲到日誌中。
之後我們可以用與 MySQL 捆綁在一起的 mysqldumpslow 命令檢查日誌。
在 WordPress 安裝時使用這些選項 ,主頁載入完成並執行後得到如下資料:
$ mysqldumpslow -g "wp_" /var/log/mysql/mysql-slow.log
Reading mysql slow query log from /var/log/mysql/mysql-slow.log
Count: 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 表示,數字則以 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: 1
select_type: SIMPLE
table: wp_options
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 433
Extra: Using where
此處,我們看到 possible_keys 是 NULL,從而確認未使用索引。
EXPLAIN 是對優化 MySQL 查詢非常強大的工具,更多資訊可以在 這裡 找到。
PostgreSQL 同樣也包括一個 EXPLAIN (該 EXPLAIN 與 MySQL 的差別很大),而 MongoDB 有$explain 元 操作符。
程式碼優化
通常只有當你不再受到 PHP 本身限制(通過使用 OpCode 快取),快取了儘可能多的內容,優化了查詢之後,才可以開始調整程式碼。
程式碼和查詢優化帶來足夠的效能提升才能建立其他快取;程式碼在最糟糕的環境(沒有快取)下效能越高,應用就越穩定,重建快取的速度也就越快。
讓我們看看如何(潛在地)優化我們的 WordPress 安裝。
首先,讓我們看看最慢的函式:
令我驚訝的是,列表中的第一項 不是 MySQL (事實上 mysql_query() 是第四),而是 apply_filter() 函式。
WordPress 程式碼庫的特點是,通過基於事件的過濾系統執行多種資料轉換,執行次序按照資料經核心、外掛新增或回撥的順序。
apply_filter() 函式是這些回撥應用的地方。
首先,你可能會注意到,函式被呼叫 4194 次。如果我們點選檢視更多細節,就可以按照“呼叫次數”降序排列“父函式”,從而發現 translate() 呼叫了apply_filter() 函式 778 次。
這很有趣,因為實際上我不使用任何翻譯。我(並懷疑大多數使用者)在使用 WordPress 軟體時都設定為本土語言:英語。
因此,讓我們點選檢視細節,進一步檢視該 translate() 函式在做什麼。
在這裡,我們看到兩間有趣的事。首先,在父函式中,有一個被呼叫了773次:__()。
檢視該函式的原始碼後,我們發現它是 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 );
}
?>
根據經驗法則,函式呼叫代價昂貴,應該儘量避免。現在我們總是呼叫 __() 而不是 translate() ,我們應該把別名改為 translate() 來保持向後相容性,而 __() 則不再呼叫非必要的函式。
然而,實際上,這種改變不會帶來多大的差異,只是微觀的優化罷了——但它的確提高了程式碼可讀性,簡化了呼叫圖。
繼續前進,讓我們看看子函式:
現在,深入該函式,我們看到有 3 個 函式或方法被呼叫,每個 778 次:
- get_translations_for_domain()
- NOOP_Translations::translate()
- apply_filters()
按照包容性實際執行時間降序排列,我們看到 apply_filter() 是目前為止耗時最長的呼叫。
檢視程式碼:
<?php
function translate( $text, $domain = 'default' ) {
$translations = get_translations_for_domain( $domain );
return apply_filters( 'gettext', $translations->translate( $text ), $text, $domain );
}
?>
這段程式碼的作用是檢索一個翻譯物件,然後將 $translations->translate() 的結果傳給 apply_filter() 。我們發現 $translations 是 NOOP_Translations 類的一個例項。
僅根據名稱(NOOP),再經程式碼中的註釋證實,我們發現翻譯器實際上沒有任何動作!
<?php
/**
* Provides the same interface as Translations, but doesn't do anything
*/
class NOOP_Translations {
?>
因此,也許我們完全可以避免這種程式碼!
通過在程式碼上進行小規模除錯,我們看到當前使用的是預設的域,我們可以修改程式碼以忽略翻譯器:
<?php
function 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 的比較執行這一特性就能找到答案。回到我們最初的執行,點選右上角的 “比較此處執行” 按鈕,並從列表中選擇新的執行。
我們發現,函式呼叫的次數減少了3% ,包容性實際執行時間減少 9% ,包容性CPU時間減少12%!
之後,可以按呼叫次數降序排列細節頁,這證實(如同我們的預期) get_translations_for_domain() 和 NOOP_Translations::translate() 函式的呼叫次數減少。同樣,可以確認沒有預料之外的變化發生。
30 分鐘的工作帶來9 - 12% 的效能提升,這非常可喜。這就意味著真實世界的效能收益,即便是在應用了 opcache 之後。
現在我們可以對其函式重複這個過程,直到找不到更多優化點。
注意:此更改已提交到 WordPress.org 並已獲更新。你可以在 WordPress Bug Tracker 跟蹤討論,檢視實踐過程。此更新計劃包含在 WordPress 4.1 版本中。
其他工具
除了出色的 XHProf/XHGui,還有一些很好的工具。
New Relic & OneAPM
New Relic 與 OneAPM 均提供前後端效能分析;洞察後臺堆疊訊息,包括 SQL 查詢與程式碼分析,前端 DOM 與 CSS 呈現,以及 Javascript 語句。OneAPM 更多功能請移步 (OneAPM 線上DEMO)
uprofiler
uprofiler 是目前還未釋出的 Facebook XHProf 分支,該分支計劃刪除 Facebook 所需的 CLA。目前,兩者具備相同的特性,只有一些部分重新命名了。
XHProf.io
XHProf.io 是 XHProf 的另一種使用者介面。XHProf.io 在配置檔案儲存使用 MySQL ,使用者友好性方面不及 XHGui。
Xdebug
在 XHProf 出現之前,Xdebug 早已存在——Xdebug 是一種主動的效能分析器,這意味著它不應該用於生產環境,但可以深入瞭解程式碼。
然而,它必須與另一個工具配合使用以讀取分析器的輸出 , 比如 KCachegrind。但是 KCachegrind 很難安裝在非 linux 機器上。另一個選擇是 Webgrind。
Webgrind 無法提供 KCachegrind 的那些特性,但它是一個 PHP Web 應用程式,在任何環境都易於安裝。
若搭配 KCachegrind ,你可以輕易探索並發現效能問題。(事實上,這是我最喜歡的剖析工具!)
結語
分析和效能調優是非常複雜的工程。有了對的工具,並理解如何善用這些工具,我們可以很大程度地提高程式碼質量——即使是對我們不熟悉的程式碼庫。
花時間去探索和學習這些工具是絕對值得的。
注意:本文是我們的 PHP 效能分析系列的第三篇,閱讀 PHP 效能分析第一篇: XHProf & XHGui 介紹 ,和 PHP 效能分析第二篇: 深入研究 XHGui 。(本文系應用效能管理領軍企業 OneAPM 工程師編譯整理)
相關文章
- PHP 效能分析(三): 效能調優實戰PHP
- 效能調優實戰
- php效能調優PHP
- TiDB 效能分析&效能調優&優化實踐大全TiDB優化
- Golang pprof 效能調優實戰,效能提升 3 倍!Golang
- JVM效能調優與實戰篇JVM
- 【效能調優】效能測試、分析與調優基礎
- TiDB 效能分析&效能調優&最佳化實踐大全TiDB
- 高效能 Java 計算服務的效能調優實戰Java
- Spring Boot Serverless 實戰系列 | 效能調優Spring BootServer
- Linux系統效能調優之效能分析Linux
- JVM效能調優與實戰進階篇-上JVM
- PHP 效能分析與實驗:效能的微觀分析PHP
- PHP 效能分析與實驗:效能的巨集觀分析PHP
- 效能測試知多少---效能分析與調優的原理
- golang 效能調優分析工具 pprof (上)Golang
- golang 效能調優分析工具 pprof(下)Golang
- 軟體效能測試分析與調優實踐之路-Java應用程式的效能分析與調優-手稿節選Java
- MySQL效能優化實戰MySql優化
- 一次效能壓測及分析調優實踐
- ElasticSearch效能調優Elasticsearch
- Nginx 效能調優Nginx
- iOS效能調優iOS
- Java效能調優Java
- Spark效能調優Spark
- oracle效能調優Oracle
- JVM效能調優與實戰基礎理論篇-下JVM
- JVM效能調優,記憶體分析工具JVM記憶體
- iOS效能調優之Analyze靜態分析iOS
- JavaEE程式在Glassfish的效能調優分析Java
- mpvue效能優化實戰技巧Vue優化
- php效能優化PHP優化
- PHP 效能優化PHP優化
- Hive效能調優實踐 - VidhyaHive
- Spark 效能調優--資源調優Spark
- Spark 效能調優--Shuffle調優 SortShuffleManagerSpark
- 【學習效能分析--第二版】如何做好效能測試分析診斷調優-暨《軟體效能測試、分析與調優實踐之路》(第2版)推薦
- 效能調優(cpu/IO/JVM記憶體分析)JVM記憶體