php 項目效能最佳化

來源:互聯網
上載者:User

C.2. 類裝載
做過Zend Framework 應用效能調優的人都知道,Zend Framework
中類裝載的開銷是相當大的。從各組件對應的大量類檔案,到類名與檔案系統非唯一對應的外掛程式的引入,大量include_once和
require_once調用可能導致嚴重的效能問題。這章將提供一些具體的策略來解決這些問題。
 C.2.1. 如何最佳化include_path?
提高類裝載速度的一個最佳化策略是合理安排include_path。具體而言,你應該做四件事情:使用絕對路徑(或絕對路徑的相對路徑[原文:paths
relative to absolute
paths]);減少包含路徑數量;ZF庫的路徑要盡量靠前;只在include_path最後包含當前路徑。
 C.2.1.1. 使用絕對路徑
儘管這看起來是一個微不足道的最佳化,然而如果不這麼做,PHP的realpath緩衝可能就無法發揮作用,結果導致opcode緩衝的效果與你的期望大相徑庭。
做到這一點有兩個簡單的途徑。第一種是在php.ini、httpd.conf或.htaccess 中寫入程式碼那些包含路徑。第二種是在設定include_path時調用PHP的realpath()函數:
 
$paths = array(
realpath(dirname(__FILE__) . '/../library'),
'.',
);
set_include_path(implode(PATH_SEPARATOR, $paths);
你也可以使用相對路徑——如果它是一個絕對路徑的相對路徑:

define('APPLICATION_PATH', realpath(dirname(__FILE__)));
$paths = array(
APPLICATION_PATH . '/../library'),
'.',
);
set_include_path(implode(PATH_SEPARATOR, $paths);
儘管如此,對一個相對路徑調用realpath()也很方便。
C.2.1.2. 減少包含路徑的數量
包含路徑將按照它們的出現順序逐個掃描。顯而易見,如果檔案在第一個路徑下的尋找速度要遠遠快於它在最後一個路徑。因此,一個明顯的改善方法是減少
include_path中的路徑數目,只包含必需的路徑。仔細檢查自己在include_path中定義的每一個路徑,確保應用用到了該路徑下的代碼,
如果沒有,則刪除。
 另外一個最佳化方法是合并路徑。例如,Zend Framework 遵循PEAR 的命名規範,因此,如果應用同時也使用了PEAR
庫(或其它遵循PEAR 命名規範的架構或組件庫),則可以把它們放在相同的包含路徑下。通常,這隻需要在一個公用目錄下為各個庫建立一個符號連結。
 C.2.1.3. 儘早定義Zend Framework庫的路徑
除了上面的建議,另外一個最佳化方法是盡量把Zend Framework庫的路徑放在include_path的前面。在大部分情況下,它應該是include_path中的第一個路徑。這樣可以保證Zend Framework自身的檔案可以在第一次掃描中命中。
 C.2.1.4. 把當前路徑放在最後或去掉
大部分include_path的例子都包含當前路徑,或‘.’。這可以方便指令碼包含目前的目錄下的檔案。並且在這些例子中,當前路徑通常是在
include_path的開頭——即首先嘗試當前路徑。對於Zend
Framework應用,這通常不是開發人員所期望的,當前路徑放在包含路徑的最後或許更適合一些。
 Example C.1. 樣本: 最佳化include_path
讓我們把上述建議綜合在一起。假設我們同時使用Zend Framework 和一些PEAR庫——如PHPUnit和Archive_Tar庫,並且偶爾也需要從目前的目錄包含檔案。
 首先,我們在項目中建立一個庫目錄。在這個目錄中,為Zend Framework庫和其它PEAR庫建立符號連結:

library
Archive/
PEAR/
PHPUnit/
Zend/
如果需要的話,還可以在此建立項目自身的庫目錄,這對那些共用庫毫無影響。
接下來,我們將在public/index.php 檔案中建立包含路徑。它會在所有請求中執行生效,不需要在別的地方再次設定。
我們將採用上面的建議:使用通過realpath()擷取的絕對路徑;把Zend Framework庫路徑放在前邊;合并包含路徑;把當前路徑放在最後。事實上,樣本做得非常漂亮——僅用了兩個路徑。
 
$paths = array(
realpath(dirname(__FILE__) . '/../library'),
'.'
);
set_include_path(implode(PATH_SEPARATOR, $paths));

C.2.2. 如何消除非必要的require_once語句
延遲載入是一項僅在需要時載入類的最佳化技術。——比如執行個體化一個對象、調用靜態類的方法或者引用類的常量或靜態屬性。PHP通過自動載入機制實現延遲載入,開發人員需要定義回呼函數,將類名映射到相應的類檔案。
 然而,如果你在庫中仍然使用require_once,那麼自動載入的效果就要大打折扣——在Zend Framework中更是如此。因此,現在的問題是:如何消除這些require_once調用,從而發揮自動載入機制的最大效能?
 C.2.2.1. 使用find和sed去除require_once調用
去除require_once 的一個簡便方法是聯合使用unix工具find和sed,把每個require_once 語句轉換為注釋。可以使用下面的命令(%是shell提示符):
 
% cd path/to/ZendFramework/library
% find . -name '*.php' -print0 | xargs -0
sed --regexp-extended --in-place 's/(require_once)/// 1/g'
這一行命令(因為閱讀方便分為兩行)遍曆每一個PHP檔案,將'require_once' 替換為'// require_once',高效地將每個require_once調用注釋掉。
 這行命令可以方便地加入到一個自動部署或發布工具中,協助提升產品的效能。不過需要注意的是,如果你使用了該技術,你就必須使用自動載入功能,這可以在項目的"public/index.php"檔案中通過下面代碼實現:
 
require_once 'Zend/Loader.php'; // one require_once is still necessary
Zend_Loader::registerAutoload();
C.2.3. 如何加快外掛程式的載入
許多組件都支援外掛程式,開發人員可以自己開發外掛程式和對應組件協同工作,也可以覆寫存在於Zend Framework包中的標準外掛程式。外掛程式給架構增加了靈活性,但是為此付出的代價是:外掛程式的載入需要耗費大量資源。
 外掛程式載入器允許開發人員註冊類首碼/路徑對,從而為外掛程式指定非標準的類檔案搜尋路徑。每個首碼允許關聯多個路徑。在內部,外掛程式載入器首先遍曆所有類首碼獲
取該類首碼關聯的路徑,然後再遍曆這些路徑尋找檔案,然後讀入檔案,測試尋找的類是否已經找到。可以想象,這將產生很多對檔案系統的stat調用。
 把這個過程乘以使用PluginLoader的組件數,你就可以知道該問題的影響範圍了。截止到本文寫作時,下面列舉的組件使用了PluginLoader: •Zend_Controller_Action_HelperBroker: 助手
•Zend_Dojo: 視圖助手,form元素和裝飾器
•Zend_File_Transfer: 適配器
•Zend_Filter_Inflector: 過濾器(ViewRenderer動作助手和Zend_Layout使用)
•Zend_Filter_Input: 過濾器和驗證器(validators)
•Zend_Form: 元素,驗證器,過濾器,裝飾器,驗證碼(captcha),檔案轉換適配器
•Zend_Paginator: 適配器
•Zend_View: 助手,過濾器

如何減少這些調用的次數呢?
C.2.3.1. 使用外掛程式載入器的包含檔案快取
Zend Framework
1.7.0為PluginLoader增加了一個包含檔案快取。該功能將記錄外掛程式的類檔案路徑,並構造"include_once"語句寫入一個檔案,從
而可以在開機檔案中包含該檔案。儘管這麼做將使你的代碼增加很多include_once調用,但是它可以讓外掛程式載入器啟動並執行更快。
 PluginLoader的文檔:includes a complete example of its use.

C.3. 國際化(i18n)和本地化(l10n)
國際化和本地化網站是擴充使用者的好方法,它保證訪問者可以獲得所需的資訊。然而,它也經常會帶來一些效能問題。下面這些策略可以用於降低國際化和本地化帶來的負擔。
 
C.3.1. 該使用哪個翻譯適配器
各種翻譯適配器千差萬別,有的以功能豐富見長,有的以效能卓越著稱。除了因業務限制而只能選擇特定適配器之外,該如何選擇一個高效率的適配器呢?
 
C.3.1.1. 使用非-XML翻譯適配器擷取最快的速度
Zend Framework中包含了許多翻譯適配器。然而它們多半使用XML格式,這會導致記憶體和效能的開銷。幸運的是,也有一些適配器是基於能夠高效解析的其它格式。按照速度從快到慢的順序,它們是:
 · Array:這是最快的,它將在包含時被直接解析為PHP的原生格式。
· CSV:使用fgetcsv()解析CSV檔案,並轉化為PHP的原生格式。
· INI:使用parse_ini_file()解析INI檔案,並轉化為PHP的原生格式。它的效能和CSV差不多。
· Gettext:Zend
Framework中的gettext適配器沒有使用gettext擴充,因為該擴充是非安全執行緒的,並且不能在一個伺服器上指定多個locale。因
此,它比直接使用gettext擴充要慢,不過gettext格式是二進位的,比解析XML要快。
 如果高效能是你的一個關注點的話,建議使用上述的某個適配器。
 
C.3.2. 如何使翻譯和本地化更快
也許因為商業因素,你只能使用基於XML的翻譯適配器。或者想讓程式更加高效,或者想讓本地化操作更快。有什麼辦法呢?
 
C.3.2.1. 使用翻譯和本地化緩衝
Zend_Translate和Zend_Locale都實現了緩衝功能,它可以明顯地提升效能。通常情況下,效能瓶頸在於讀入檔案,而不是實際的尋找操作,使用緩衝可以避免翻譯和本地化檔案的重複讀取。
 你可以從下面連結中進一步瞭解翻譯和本地化緩衝的內容:
· Zend_Translate adapter caching
· Zend_Locale caching
 
C.4. 視圖渲染
如果使用Zend
Framework的MVC架構,你可能會使用Zend_View組件。相對於其它視圖或模板引擎,Zend_View是相當高效的。因為直接用PHP編
寫視圖指令碼,就沒有編譯自訂標籤的負擔,也不需要擔心編譯後指令碼的最佳化。當然,
Zend_View也存在自己的問題:擴充的代價很大(視圖助手),如果通過許多視圖助手來完成關鍵功能,將會導致很大的效能開銷。
 
C.4.1. 如何加快視圖助手的解析
Zend_View中的大部分方法實際上都是通過助手系統的重載來實現的。它賦予Zend_View極大的靈活性,不需要擴充Zend_View,提供應
用所需的助手方法就可以,開發人員在單獨的類中定義助手方法,然後就可以像調用定義在Zend_View中的方法那樣使用助手方法。這樣既可以保持視圖對象
的簡單性,也可以保證助手只在需要時被建立。
 在內部,Zend_View通過外掛程式載入器尋找助手類檔案。這意味著每一次調用助手,Zend_View需要把助手名字傳給外掛程式載入器,由它決定類名,
如果未載入則載入助手,然後返回執行個體化的對象。因為Zend_View在內部緩衝了已經載入的助手,所以下次再使用這個助手時,速度將會快很多,然而,如
果應用中使用了很多助手,這個過程對系統效能的影響可能會很大。
 那接下來的問題是:如何加速助手的解析?
 
C.4.1.1. 使用PluginLoader的包含檔案快取功能
這是最簡單經濟的方法,具體見C.2.3.1中所述。曾經有實驗表明,這個技術在沒有opcode緩衝情形下可以提高25-30%的效能,如果有opcode緩衝,則可以提高到40-65%。
 
C.4.1.2. 擴充Zend_View來提供常用助手方法
另外一個提高效能的方法是擴充Zend_View,在子類中手動增加應用常用的助手方法。這些助手方法可以簡單地作為一個代理,執行個體化一個相應助手類完成任務,當然也可以自己實現任務的所有操作。
 class My_View extends Zend_View{ /** * @var array Registry of helper
classes used */ protected $_localHelperObjects = array(); /** * Proxy to
url view helper * * @param array $urlOptions Options passed to the
assemble method of the Route object. * @param mixed $name The name of a
Route to use. If null it will use the current Route * @param bool $reset
Whether or not to reset the route defaults with those provided *
@return string Url for the link href attribute. */ public function
url(array $urlOptions = array(), $name = null, $reset = false, $encode =
true ) { if (!array_key_exists('url', $this->_localHelperObjects)) {
$this->_localHelperObjects['url'] = new Zend_View_Helper_Url();
$this->_localHelperObjects['url']->setView($view); } $helper =
$this->_localHelperObjects['url']; return
$helper->url($urlOptions, $name, $reset, $encode); } /** * Echo a
message * * Direct implementation. * * @param string $string * @return
string */ public function message($string) { return "" .
$this->escape($message) . "n"; }}
無論哪種方式,該方法將大量降低助手系統的效能消耗,它完全避免了外掛程式載入器的調用,同時還充分利用了自動載入機制和按需載入的特性。
 
C.4.2. 如何提高地區視圖(view partials)效能
如果開發人員在應用中大量使用地區(partials),那麼他會經常發現partial() 視圖助手會因為複製視圖對象而產生效能瓶頸。有辦法提高它的速度嗎?
 
C.4.2.1. 僅在真正需要時使用partial()
partial()視圖助手接收三個參數:
· $name: 視圖指令碼的名字
· $module: 視圖指令碼所在的模組名,或者當沒有第三個參數且是一個對象或數組時,它將作為$model參數。
· $model: 數組或對象,作為乾淨的資料賦值給視圖,用於解析地區視圖。
partial()的強大之處在於第二與第三個參數。$module參數允許臨時加入指定模組的視圖路徑,從而解析該模組下的地區視圖指令碼;$model參數允許你顯式地為地區視圖指定變數。如果你根本沒有用到這兩個參數,那麼請用render()代替!
 基本上,除非你需要為地區視圖傳入變數並需要一個乾淨的變數環境,或者渲染另外一個MVC模組的視圖,你沒有必要使用partial()。相反,你應該使用Zend_View內建的render()方法渲染視圖。
 
C.4.3. 如何提高action()視圖助手效能
1.5.0版本引入了action()視圖助手,允許開發人員分發一個MVC動作並捕獲它的輸出。它向DRY原則邁近了一大步,促進了代碼的複用。但是這也
是一個代價高昂的操作。在action()視圖助手內部,它會複製請求和響應對象,調用分發器,然後調用對應的控制器和動作等等。
 如何提高它的速度呢?
 
C.4.3.1. 盡量使用ActionStack代替
與action()視圖助手同時加入ZF的動作堆棧,由一個動作助手和前端控制器外掛程式組成。它允許你將分發周期中需要調用的附加動作壓入一個棧中。如果你
在布局視圖指令碼中使用了action(),它可以用動作堆棧替換,完成視圖的渲染,實現響應片段的分離。作為例子,你可以像下面那樣實現一個
dispatchLoopStartup() 外掛程式,為每個頁面添加一個登入表單輸入框:
 class LoginPlugin extends Zend_Controller_Plugin_Abstract{ protected
$_stack; public function dispatchLoopStartup(
Zend_Controller_Request_Abstract $request ) { $stack =
$this->getStack(); $loginRequest = new
Zend_Controller_Request_Simple();
$loginRequest->setControllerName('user') ->setActionName('index')
->setParam('responseSegment', 'login');
$stack->pushStack($loginRequest); } public function getStack() { if
(null === $this->_stack) { $front =
Zend_Controller_Front::getInstance(); if
(!$front->hasPlugin('Zend_Controller_Plugin_ActionStack')) { $stack =
new Zend_Controller_Plugin_ActionStack();
$front->registerPlugin($stack); } else { $stack =
$front->getPlugin('ActionStack') } $this->_stack = $stack; }
return $this->_stack; }}
 然後UserController::indexAction()方法就可以通過responseSegment參數指定渲染哪個響應片段。在布局指令碼中,你可以簡單地輸出該響應片段:
 layout()->login ?>
儘管ActionStack需要一個分發周期,但是它比action()視圖助手效率要高,因為它不需要複製對象及重設它們的狀態。此外,它可以確保所有的pre/post分發外掛程式會被調用,如果你是通過前端控制器外掛程式實現ACL,那麼這一點就顯得特別重要。
 
C.4.3.2. 查詢模型時使用視圖助手代替action()
大部分情況下,使用action()是小題大做。如果大部分的商務邏輯都封裝在模型中,而你只是簡單地查詢模型,並將結果傳遞給視圖指令碼的話,通過一個視圖助手擷取模型,查詢資料,完成相應的操作,將是一個更高效簡潔的方法。
 作為一個例子,考慮下面的控制器動作和視圖指令碼:
class BugController extends Zend_Controller_Action{ public function
listAction() { $model = new Bug(); $this->view->bugs =
$model->fetchActive(); }} // bug/list.phtml:echo "n";foreach
($this->bugs as $bug) { printf("•%s: %sn",
$this->escape($bug->id), $this->escape($bug->summary));

}echo "n";
使用action(),將像下面這樣調用:
action('list', 'bug') ?>
這可以重構為通過視圖助手實現,如下所示:
class My_View_Helper_BugList extends Zend_View_Helper_Abstract{ public
function direct() { $model = new Bug(); $html = "n"; foreach
($model->fetchActive() as $bug) { $html .= sprintf( "•%s: %sn",

 $this->view->escape($bug->id), $this->view->escape($bug->summary) ); } $html .= "n"; return $html; }}
 然後像下面這樣調用助手:
bugList() ?>
這麼做有兩個好處:消除了action()助手的開銷,並且該API更易於理解。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.