Php accelerator

 

(一)簡介


  本文嘗試對PHP程序加速的各個方面進行探索,包括必要性以及從不同角度採取的具體措施。希望有助於讀者瞭解PHP程序加速,並應用於實際。


(二)是否需要加速?


  這個問題聽起來有點愚蠢。在這個時代,很少有人會懷疑時間是最寶貴的財富,尤其是在商業市場上。程序執行越快,用戶就節約越多的時間,這樣你的程序就可以用更少的時間和服務器資源爲用戶服務,從而產生更多效益。我想對於大部份人(包括我自己)來說,很多WEB項目都是在很緊張的時間裏完成的,通常沒有經過縝密的思考和嚴格的測試。當開始一個新的WEB項目。很多人都在構建那種“快而亂”的應用,缺乏必要的時間來調整和改良代碼,這時優化和加速就是我們必須採取的措施。  然而需要特別指出的是,並不是所有程序都需要加速。  優化已完成的代碼是很浪費時間的,最好的方法是在寫代碼的時候就注意到效率,然後完成項目後只優化確實需要優化的那部份。一般一個程序只會有少數幾個影響速度的瓶頸,將它們找出來並解決掉,程序就可以很好地運行。另外,當遇到執行效率低下的情況,首先要用大局的眼光來找出影響效率的主要因素,而不要拘泥於細節—例如數據量過大,服務器帶寬不夠,或硬件配置過低,在這樣的情況下,優化代碼於事無補。  另外,在沒有發現程序有明顯的執行緩慢的跡象時,就不要太吹毛求疵,爲了改進一些非常細節的代碼而浪費時間。用這些時間,你可以完成另一個項目或爲原來的項目完成一個擴展功能。當然你可以笑話我不夠負責,沒有把工作做得盡善盡好,我也可以說你是完美主義者:-)    綜上,在你決定爲你的PHP程序提速之前,問問自己是否有必要。


(三)如何加速?


要回答“如何加速”這個問題前,需要先回答以下兩個小問題:你的程序慢在哪一部份? PHP可以從哪幾個方面考慮加速?  第一個小問題顯然我無法給你答案,但我建議你用“測試腳本執行速度”的方法來解決。只有找出限制速度的瓶頸,才能考慮如何去解決。  第二個小問題我大概的答案是:代碼優化,壓縮輸出,內容緩存輸出,函數緩存輸出,加速/緩存工具軟件。如果你知道更多請告訴我:-)   下面我們來詳細地研究一下這幾個方面的相關技術。當然實際上每個方面都有無數的細節可以討論,下面的內容難免會有片面的地方,歡迎補充。


<1> 測試

◆ 服務器負載測試


  服務器負載太大而影響程序效率也是很常見的,我們需要對此進行測試。這裏我以目前最常用的Apache服務器爲例。  Apache服務器自帶有一個叫AB(ApacheBench)的工具,在bin目錄下。使用這個輕巧的工具我們可以對服務器進行負載測試,看看在重負荷之下服務器的表現如何。ApacheBench 可以針對某個特定的 URL 仿真出連續的聯機請求,同時還可以仿真出同時間點數個相同的聯機請求,因此利用 ApacheBench 可幫助我們在網站開發期間仿真實際上線可能的情況,利用仿真出來的數據作爲調整服務器設定或程序的依據。  在命令行下輸出:

./ab -n number_of_total_requests / -c number_of_simultaneous_requests / http://your_web_server/your_php_app.php

例如:

./ab -n 1000 -c 50 http://www.domain.com/myapp.php

  AB將同時向http://www.domain.com/myapp.php發出50個併發請求,共發出1000次。   測試結果將可能是這樣的:

 Server Software: Apache/2.0.16 Server Hostname: localhost Server Port: 80 Document Path: /myapp.php Document Length: 1311 bytes Concurrency Level: 50 Time taken for tests: 8.794 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 1754000 bytes HTML transferred: 1311000 bytes Requests per second: 113.71 Transfer rate: 199.45 kb/s received Connection Times (ms) min avg max Connect: 0 0 5 Processing: 111 427 550 Total: 111 427 555

  myapp.php每秒鐘可以處理的請求數爲113.71個。將請求數增加,看看服務器能否處理更大的壓力。你也需要調節Apache的MaxClients, ThreadsPerChild, MaxThreadsPerChild 等參數,基於你的 httpd.conf 中的 MPM 模塊選擇。  如果你想得到更詳細的信息,請到www.apache.org上查閱一些更深入的文檔,包括模塊和第三方的提高效率的工具。修改httpd.conf後,要重啓Apache服務器,然後再用AB測試。你會看到每秒請求數增加或減少。  記下每次的參數,最後選擇最佳效率的那種配置。  要指出的是,除了AB,還有許多優秀的服務器性能測試軟件。另外,如果你的服務器不是 Apache,請自行尋找測試方法。

◆ 腳本執行速度測試

  前面有提到,只有找到影響速度的代碼,我們纔有可能進行優化。PEAR的benchmark包中的Benchmark_Timer類和Benchmark_Iterate類,可以用來很方便地測試腳本執行的速度。(關於PEAR的安裝與配置請自行查看相關資料) 首先用Benchmark_Iterate類來測試程序中某個函數或類的某個方法的執行時間。

benchmark1.php

<?php require_once('Benchmark/Iterate.php'); $benchmark = new Benchmark_Iterate(); $benchmark->run(10, 'myFunction','test'); $result = $benchmark->get(); echo " "; print_r($result); echo " "; exit; function myFunction($var) { // do something echo 'Hello '; } ?>

  建立benchmark Iterate對象$benchmark,這個對象用來執行myFunction函數10次。  $argument變量每次都傳遞給myFunction. 多次運行的分析結果存入$result,然後用benchmark對象的get()方法來獲取。這個結果用print_r()輸出到屏幕。通常會輸出這樣的結果:  Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Array( [1] => 0.000427 [2] => 0.000079 [3] => 0.000072 [4] => 0.000071 [5] => 0.000076 [6] => 0.000070 [7] => 0.000073 [8] => 0.000070 [9] => 0.000074 [10] => 0.000072 [mean] => 0.000108 [iterations] => 10)  myFunction的每次執行,benchmark對象都會跟蹤執行時間。並且會計算平均的執行時間([mean]那一行)。通過多次運行目標函數,你可以得到該函數的平均運行時間。  在實際測試中,函數的次數應當至少1000次左右,這樣可以得到較客觀的結果。

  現在我們看看另一個測試腳本運行時間的方法--使用Benchmark_Timer類來測試一段代碼執行所消耗的時間及這一段代碼中每次調用與下一次調用間的時間。

benchmark2.php

<?php require_once 'Benchmark/Timer.php'; $timer = new Benchmark_Timer(); 
$timer->start(); 
$timer->setMarker('start_myFunction'); 

f r($i=0; $i<10; $i++){ 
                                                                                                                                                               

  首先,建立一個benchmark timer對象$timer。然後調用start()方法,表示開始計時。 SetMaker()方法用來標記要測試的代碼段。MyFunction()函數在循環中被調用,表示一段要執行的代碼(當然實際中不會這麼簡單)。然後再用$timer對象的setMarker()方法標記程序執行終點。分析信息用getProfiling()來獲取。在兩個標記間程序執行消耗的時間用timeElapsed()方法計算出來(就像例子中的循環)。最後,用print_r()輸出信息到屏幕:

Array( [0] => Array ( [name] => Start [time] => 1085730111.27175200 [diff] => - [total] => 1085730111.271752 ) [1] => Array ( [name] => start_myFunction [time] => 1085730111.27203800 [diff] => 0.000286 [total] => 1085730111.272038 ) [2] => Array ( [name] => end_myFunction [time] => 1085730111.27263200 [diff] => 0.000594 [total] => 1085730111.272632 ) [3] => Array ( [name] => Stop [time] => 1085730111.27271800 [diff] => 0.000086 [total] => 1085730111.272718 ))0 1 2 3 4 5 6 7 8 9 Time elapsed: 0.000594

  通過這種方法,你可以在代碼中設置大量時間段標記,獲取每段代碼執行時消耗的時間,很容易可以看出到底是哪一部份的代碼影響了整個程序的運行效率。然後開始着手對這部份代碼進行改進。  用以上兩種方法,你可以找出代碼中最影響速度的部份代碼。另外還可以用來對優化後的代碼進行測試,看看到底執行速度提高了多少。通過測試->優化->測試->優化 …這樣不斷循環,你可以最終確定提供最佳效率的代碼。

<2> 加速

◆ 代碼優化

  掌握了PEAR::BenchMark,現在你已經知道如何測試你的代碼,知道如何判斷你的代碼是快是慢,是哪一部份比較慢。那麼接下來我要說的就是如何消滅或優化那部份慢的代碼。這一點上我個人最主要的經驗只有兩點,一是消除錯誤的或低效的循環;二是優化數據庫查詢語句。其實還存在一些其它的優化細節,比如“str_replace比ereg_replace快”、“echo比print快”等等。這些我暫時都放在一邊,稍後我會提到用緩存來對付過於頻繁的IO。下面我們將三個功能相同,但程序寫法不同的函數的效率(消耗的時間)進行對比。

badloops.php

 <?php require_once('Benchmark/Iterate.php'); define('MAX_RUN',100); $data = array(1, 2, 3, 4, 5); doBenchmark('v1', $data); doBenchmark('v2', $data); doBenchmark('v3', $data); function doBenchmark($functionName = null, $arr = null) { reset($arr); $benchmark = new Benchmark_Iterate; $benchmark->run(MAX_RUN, $functionName, $arr); $result = $benchmark->get(); echo '<br>'; printf("%s ran %d times where average exec time %.5f ms",$functionName,$result['iterations'],$result['mean'] * 1000); } function v1($myArray = null) { // 效率很差的循環 for ($i =0; $i < sizeof($myArray); $i++) { echo '<!--' . $myArray[$i] . ' --> '; } } function v2($myArray = null) { // 效率略有提高 $max = sizeof($myArray); for ($i =0; $i < $max ; $i++) { echo '<!--' . $myArray[$i] . ' --> '; } } function v3($myArray = null){ //最佳效率 echo "<!--", implode(" --> <!--", $myArray), " --> "; } ?>

  程序輸出的結果大概是這樣的:

v1 ran 100 times where average exec time 0.18400 ms v2 ran 100 times where average exec time 0.15500 ms v3 ran 100 times where average exec time 0.09100 ms

  可以看到,函數的執行時間變少,效率上升。  函數v1有個很明顯的錯誤,每一次循環的時間,都需要調用sizeof()函數來計算。函數v2則在循環外把$myArray數組的元素個數存到$max變量中,避免了每次循環都要計算數組的元素個數,所以效率提高了。函數v3的效率最高,利用了現成的函數,避免循環。  這個例子只是給你一個感性的認識,明白什麼是相對高效的代碼。在實際開發中,我相信會有很多人會迷迷糊糊地寫出很多低效率的代碼。要把代碼寫得精煉而高效,恐怕需要時間去錘鍊:-) 但這是另一個話題了,我們略過不談。  數據庫應用基本上每個PHP程序都會用到,在實際開發中我發現最影響整個系統效率的就是數據庫這部份。至於數據庫的優化和數據查詢語句的優化,在此限於篇幅不詳細討論。你可以參看這兩篇文章: http://www.phpe.net/articles/340.shtml http://www.phpe.net/articles/323.shtml   及這篇討論: http://club.phpe.net/index.php?s=&act=ST&f=15&t=4783&st=0(前面幾篇貼子總結不錯),主要是針對MySQL的。

◆壓縮輸出 gzip

  利用Apache中的mod_gzip模塊,我們可以利用gzip的壓縮算法來對Apache服務器發佈的網頁內容進行壓縮後再傳輸到客戶端的瀏覽器。如果是純文本的內容,效果非常明顯,大約可以壓縮到原來的30%-40%,使用戶的瀏覽速度大大加快。  Gzip需要客戶端瀏覽器支持,目前大部份瀏覽器都支持gzip,如IE,Netscape,Mozilla等,所以這種方法值得一試。我們可以利用PHP中的預定義變量$_SERVER[‘HTTP_ACCEPT_ENCODING’]來判斷客戶端瀏覽器是否支持gzip。

gzip1.php

<?php if(ereg('gzip',

(一)簡介

  本文嘗試對PHP程序加速的各個方面進行探索,包括必要性以及從不同角度採取的具體措施。希望有助於讀者瞭解PHP程序加速,並應用於實際。

(二)是否需要加速?

  這個問題聽起來有點愚蠢。在這個時代,很少有人會懷疑時間是最寶貴的財富,尤其是在商業市場上。程序執行越快,用戶就節約越多的時間,這樣你的程序就可以用更少的時間和服務器資源爲用戶服務,從而產生更多效益。我想對於大部份人(包括我自己)來說,很多WEB項目都是在很緊張的時間裏完成的,通常沒有經過縝密的思考和嚴格的測試。當開始一個新的WEB項目。很多人都在構建那種“快而亂”的應用,缺乏必要的時間來調整和改良代碼,這時優化和加速就是我們必須採取的措施。  然而需要特別指出的是,並不是所有程序都需要加速。  優化已完成的代碼是很浪費時間的,最好的方法是在寫代碼的時候就注意到效率,然後完成項目後只優化確實需要優化的那部份。一般一個程序只會有少數幾個影響速度的瓶頸,將它們找出來並解決掉,程序就可以很好地運行。另外,當遇到執行效率低下的情況,首先要用大局的眼光來找出影響效率的主要因素,而不要拘泥於細節—例如數據量過大,服務器帶寬不夠,或硬件配置過低,在這樣的情況下,優化代碼於事無補。  另外,在沒有發現程序有明顯的執行緩慢的跡象時,就不要太吹毛求疵,爲了改進一些非常細節的代碼而浪費時間。用這些時間,你可以完成另一個項目或爲原來的項目完成一個擴展功能。當然你可以笑話我不夠負責,沒有把工作做得盡善盡好,我也可以說你是完美主義者:-)    綜上,在你決定爲你的PHP程序提速之前,問問自己是否有必要。

(三)如何加速?

要回答“如何加速”這個問題前,需要先回答以下兩個小問題:你的程序慢在哪一部份? PHP可以從哪幾個方面考慮加速?  第一個小問題顯然我無法給你答案,但我建議你用“測試腳本執行速度”的方法來解決。只有找出限制速度的瓶頸,才能考慮如何去解決。  第二個小問題我大概的答案是:代碼優化,壓縮輸出,內容緩存輸出,函數緩存輸出,加速/緩存工具軟件。如果你知道更多請告訴我:-)   下面我們來詳細地研究一下這幾個方面的相關技術。當然實際上每個方面都有無數的細節可以討論,下面的內容難免會有片面的地方,歡迎補充。

<1> 測試

◆ 服務器負載測試

  服務器負載太大而影響程序效率也是很常見的,我們需要對此進行測試。這裏我以目前最常用的Apache服務器爲例。  Apache服務器自帶有一個叫AB(ApacheBench)的工具,在bin目錄下。使用這個輕巧的工具我們可以對服務器進行負載測試,看看在重負荷之下服務器的表現如何。ApacheBench 可以針對某個特定的 URL 仿真出連續的聯機請求,同時還可以仿真出同時間點數個相同的聯機請求,因此利用 ApacheBench 可幫助我們在網站開發期間仿真實際上線可能的情況,利用仿真出來的數據作爲調整服務器設定或程序的依據。  在命令行下輸出:

./ab -n number_of_total_requests / -c number_of_simultaneous_requests / http://your_web_server/your_php_app.php

例如:

./ab -n 1000 -c 50 http://www.domain.com/myapp.php

  AB將同時向http://www.domain.com/myapp.php發出50個併發請求,共發出1000次。   測試結果將可能是這樣的:

 Server Software: Apache/2.0.16 Server Hostname: localhost Server Port: 80 Document Path: /myapp.php Document Length: 1311 bytes Concurrency Level: 50 Time taken for tests: 8.794 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 1754000 bytes HTML transferred: 1311000 bytes Requests per second: 113.71 Transfer rate: 199.45 kb/s received Connection Times (ms) min avg max Connect: 0 0 5 Processing: 111 427 550 Total: 111 427 555 

  myapp.php每秒鐘可以處理的請求數爲113.71個。將請求數增加,看看服務器能否處理更大的壓力。你也需要調節Apache的MaxClients, ThreadsPerChild, MaxThreadsPerChild 等參數,基於你的 httpd.conf 中的 MPM 模塊選擇。  如果你想得到更詳細的信息,請到www.apache.org上查閱一些更深入的文檔,包括模塊和第三方的提高效率的工具。修改httpd.conf後,要重啓Apache服務器,然後再用AB測試。你會看到每秒請求數增加或減少。  記下每次的參數,最後選擇最佳效率的那種配置。  要指出的是,除了AB,還有許多優秀的服務器性能測試軟件。另外,如果你的服務器不是 Apache,請自行尋找測試方法。

◆ 腳本執行速度測試

  前面有提到,只有找到影響速度的代碼,我們纔有可能進行優化。PEAR的benchmark包中的Benchmark_Timer類和Benchmark_Iterate類,可以用來很方便地測試腳本執行的速度。(關於PEAR的安裝與配置請自行查看相關資料) 首先用Benchmark_Iterate類來測試程序中某個函數或類的某個方法的執行時間。

benchmark1.php

<?php require_once('Benchmark/Iterate.php'); $benchmark = new Benchmark_Iterate(); $benchmark->run(10, 'myFunction','test'); $result = $benchmark->get(); echo " "; print_r($result); echo " "; exit; function myFunction($var) { // do something echo 'Hello '; } ?> 

  建立benchmark Iterate對象$benchmark,這個對象用來執行myFunction函數10次。  $argument變量每次都傳遞給myFunction. 多次運行的分析結果存入$result,然後用benchmark對象的get()方法來獲取。這個結果用print_r()輸出到屏幕。通常會輸出這樣的結果:  Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Array( [1] => 0.000427 [2] => 0.000079 [3] => 0.000072 [4] => 0.000071 [5] => 0.000076 [6] => 0.000070 [7] => 0.000073 [8] => 0.000070 [9] => 0.000074 [10] => 0.000072 [mean] => 0.000108 [iterations] => 10)  myFunction的每次執行,benchmark對象都會跟蹤執行時間。並且會計算平均的執行時間([mean]那一行)。通過多次運行目標函數,你可以得到該函數的平均運行時間。  在實際測試中,函數的次數應當至少1000次左右,這樣可以得到較客觀的結果。

  現在我們看看另一個測試腳本運行時間的方法--使用Benchmark_Timer類來測試一段代碼執行所消耗的時間及這一段代碼中每次調用與下一次調用間的時間。

benchmark2.php

<?php require_once 'Benchmark/Timer.php'; $timer = new Benchmark_Timer(); $timer->start(); $timer->setMarker('start_myFunction'); for($i=0; $i<10; $i++){ myFunction($argument); } $timer->setMarker('end_myFunction'); $timer->stop(); $profiling = $timer->getProfiling(); echo ' Time elapsed: ' . $timer->timeElapsed('start_myFunction','end_myFunction') .' '; echo ' '; print_r($profiling); echo ' '; exit; function myFunction($var) { static $counter = 0; // do something echo $counter++ . ' '; } ?> 

  首先,建立一個benchmark timer對象$timer。然後調用start()方法,表示開始計時。 SetMaker()方法用來標記要測試的代碼段。MyFunction()函數在循環中被調用,表示一段要執行的代碼(當然實際中不會這麼簡單)。然後再用$timer對象的setMarker()方法標記程序執行終點。分析信息用getProfiling()來獲取。在兩個標記間程序執行消耗的時間用timeElapsed()方法計算出來(就像例子中的循環)。最後,用print_r()輸出信息到屏幕:

Array( [0] => Array ( [name] => Start [time] => 1085730111.27175200 [diff] => - [total] => 1085730111.271752 ) [1] => Array ( [name] => start_myFunction [time] => 1085730111.27203800 [diff] => 0.000286 [total] => 1085730111.272038 ) [2] => Array ( [name] => end_myFunction [time] => 1085730111.27263200 [diff] => 0.000594 [total] => 1085730111.272632 ) [3] => Array ( [name] => Stop [time] => 1085730111.27271800 [diff] => 0.000086 [total] => 1085730111.272718 ))0 1 2 3 4 5 6 7 8 9 Time elapsed: 0.000594 

  通過這種方法,你可以在代碼中設置大量時間段標記,獲取每段代碼執行時消耗的時間,很容易可以看出到底是哪一部份的代碼影響了整個程序的運行效率。然後開始着手對這部份代碼進行改進。  用以上兩種方法,你可以找出代碼中最影響速度的部份代碼。另外還可以用來對優化後的代碼進行測試,看看到底執行速度提高了多少。通過測試->優化->測試->優化 …這樣不斷循環,你可以最終確定提供最佳效率的代碼。

<2> 加速

◆ 代碼優化

  掌握了PEAR::BenchMark,現在你已經知道如何測試你的代碼,知道如何判斷你的代碼是快是慢,是哪一部份比較慢。那麼接下來我要說的就是如何消滅或優化那部份慢的代碼。這一點上我個人最主要的經驗只有兩點,一是消除錯誤的或低效的循環;二是優化數據庫查詢語句。其實還存在一些其它的優化細節,比如“str_replace比ereg_replace快”、“echo比print快”等等。這些我暫時都放在一邊,稍後我會提到用緩存來對付過於頻繁的IO。下面我們將三個功能相同,但程序寫法不同的函數的效率(消耗的時間)進行對比。

badloops.php

 <?php require_once('Benchmark/Iterate.php'); define('MAX_RUN',100); $data = array(1, 2, 3, 4, 5); doBenchmark('v1', $data); doBenchmark('v2', $data); doBenchmark('v3', $data); function doBenchmark($functionName = null, $arr = null) { reset($arr); $benchmark = new Benchmark_Iterate; $benchmark->run(MAX_RUN, $functionName, $arr); $result = $benchmark->get(); echo '<br>'; printf("%s ran %d times where average exec time %.5f ms",$functionName,$result['iterations'],$result['mean'] * 1000); } function v1($myArray = null) { // 效率很差的循環 for ($i =0; $i < sizeof($myArray); $i++) { echo '<!--' . $myArray[$i] . ' --> '; } } function v2($myArray = null) { // 效率略有提高 $max = sizeof($myArray); for ($i =0; $i < $max ; $i++) { echo '<!--' . $myArray[$i] . ' --> '; } } function v3($myArray = null){ //最佳效率 echo "<!--", implode(" --> <!--", $myArray), " --> "; } ?> 

  程序輸出的結果大概是這樣的:

v1 ran 100 times where average exec time 0.18400 ms v2 ran 100 times where average exec time 0.15500 ms v3 ran 100 times where average exec time 0.09100 ms

  可以看到,函數的執行時間變少,效率上升。  函數v1有個很明顯的錯誤,每一次循環的時間,都需要調用sizeof()函數來計算。函數v2則在循環外把$myArray數組的元素個數存到$max變量中,避免了每次循環都要計算數組的元素個數,所以效率提高了。函數v3的效率最高,利用了現成的函數,避免循環。  這個例子只是給你一個感性的認識,明白什麼是相對高效的代碼。在實際開發中,我相信會有很多人會迷迷糊糊地寫出很多低效率的代碼。要把代碼寫得精煉而高效,恐怕需要時間去錘鍊:-) 但這是另一個話題了,我們略過不談。  數據庫應用基本上每個PHP程序都會用到,在實際開發中我發現最影響整個系統效率的就是數據庫這部份。至於數據庫的優化和數據查詢語句的優化,在此限於篇幅不詳細討論。你可以參看這兩篇文章: http://www.phpe.net/articles/340.shtml http://www.phpe.net/articles/323.shtml   及這篇討論: http://club.phpe.net/index.php?s=&act=ST&f=15&t=4783&st=0(前面幾篇貼子總結不錯),主要是針對MySQL的。

◆壓縮輸出 gzip

  利用Apache中的mod_gzip模塊,我們可以利用gzip的壓縮算法來對Apache服務器發佈的網頁內容進行壓縮後再傳輸到客戶端的瀏覽器。如果是純文本的內容,效果非常明顯,大約可以壓縮到原來的30%-40%,使用戶的瀏覽速度大大加快。  Gzip需要客戶端瀏覽器支持,目前大部份瀏覽器都支持gzip,如IE,Netscape,Mozilla等,所以這種方法值得一試。我們可以利用PHP中的預定義變量$_SERVER[‘HTTP_ACCEPT_ENCODING’]來判斷客戶端瀏覽器是否支持gzip。

gzip1.php

___FCKpd___5

  接下來我們對上面這個PHP程序進行擴展,使用ob_start(ob_gzhandler)來將網頁內容壓縮,存入緩衝併發送給支持gzip的瀏覽器,瀏覽器會自動將壓縮後的內容解壓,顯示。

gzip2.php

<?php define('MAX',100); if(ereg('gzip',

(一)簡介

  本文嘗試對PHP程序加速的各個方面進行探索,包括必要性以及從不同角度採取的具體措施。希望有助於讀者瞭解PHP程序加速,並應用於實際。

(二)是否需要加速?

  這個問題聽起來有點愚蠢。在這個時代,很少有人會懷疑時間是最寶貴的財富,尤其是在商業市場上。程序執行越快,用戶就節約越多的時間,這樣你的程序就可以用更少的時間和服務器資源爲用戶服務,從而產生更多效益。我想對於大部份人(包括我自己)來說,很多WEB項目都是在很緊張的時間裏完成的,通常沒有經過縝密的思考和嚴格的測試。當開始一個新的WEB項目。很多人都在構建那種“快而亂”的應用,缺乏必要的時間來調整和改良代碼,這時優化和加速就是我們必須採取的措施。  然而需要特別指出的是,並不是所有程序都需要加速。  優化已完成的代碼是很浪費時間的,最好的方法是在寫代碼的時候就注意到效率,然後完成項目後只優化確實需要優化的那部份。一般一個程序只會有少數幾個影響速度的瓶頸,將它們找出來並解決掉,程序就可以很好地運行。另外,當遇到執行效率低下的情況,首先要用大局的眼光來找出影響效率的主要因素,而不要拘泥於細節—例如數據量過大,服務器帶寬不夠,或硬件配置過低,在這樣的情況下,優化代碼於事無補。  另外,在沒有發現程序有明顯的執行緩慢的跡象時,就不要太吹毛求疵,爲了改進一些非常細節的代碼而浪費時間。用這些時間,你可以完成另一個項目或爲原來的項目完成一個擴展功能。當然你可以笑話我不夠負責,沒有把工作做得盡善盡好,我也可以說你是完美主義者:-)    綜上,在你決定爲你的PHP程序提速之前,問問自己是否有必要。

(三)如何加速?

要回答“如何加速”這個問題前,需要先回答以下兩個小問題:你的程序慢在哪一部份? PHP可以從哪幾個方面考慮加速?  第一個小問題顯然我無法給你答案,但我建議你用“測試腳本執行速度”的方法來解決。只有找出限制速度的瓶頸,才能考慮如何去解決。  第二個小問題我大概的答案是:代碼優化,壓縮輸出,內容緩存輸出,函數緩存輸出,加速/緩存工具軟件。如果你知道更多請告訴我:-)   下面我們來詳細地研究一下這幾個方面的相關技術。當然實際上每個方面都有無數的細節可以討論,下面的內容難免會有片面的地方,歡迎補充。

<1> 測試

◆ 服務器負載測試

  服務器負載太大而影響程序效率也是很常見的,我們需要對此進行測試。這裏我以目前最常用的Apache服務器爲例。  Apache服務器自帶有一個叫AB(ApacheBench)的工具,在bin目錄下。使用這個輕巧的工具我們可以對服務器進行負載測試,看看在重負荷之下服務器的表現如何。ApacheBench 可以針對某個特定的 URL 仿真出連續的聯機請求,同時還可以仿真出同時間點數個相同的聯機請求,因此利用 ApacheBench 可幫助我們在網站開發期間仿真實際上線可能的情況,利用仿真出來的數據作爲調整服務器設定或程序的依據。  在命令行下輸出:

./ab -n number_of_total_requests / -c number_of_simultaneous_requests / http://your_web_server/your_php_app.php

例如:

./ab -n 1000 -c 50 http://www.domain.com/myapp.php

  AB將同時向http://www.domain.com/myapp.php發出50個併發請求,共發出1000次。   測試結果將可能是這樣的:

 Server Software: Apache/2.0.16 Server Hostname: localhost Server Port: 80 Document Path: /myapp.php Document Length: 1311 bytes Concurrency Level: 50 Time taken for tests: 8.794 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 1754000 bytes HTML transferred: 1311000 bytes Requests per second: 113.71 Transfer rate: 199.45 kb/s received Connection Times (ms) min avg max Connect: 0 0 5 Processing: 111 427 550 Total: 111 427 555 

  myapp.php每秒鐘可以處理的請求數爲113.71個。將請求數增加,看看服務器能否處理更大的壓力。你也需要調節Apache的MaxClients, ThreadsPerChild, MaxThreadsPerChild 等參數,基於你的 httpd.conf 中的 MPM 模塊選擇。  如果你想得到更詳細的信息,請到www.apache.org上查閱一些更深入的文檔,包括模塊和第三方的提高效率的工具。修改httpd.conf後,要重啓Apache服務器,然後再用AB測試。你會看到每秒請求數增加或減少。  記下每次的參數,最後選擇最佳效率的那種配置。  要指出的是,除了AB,還有許多優秀的服務器性能測試軟件。另外,如果你的服務器不是 Apache,請自行尋找測試方法。

◆ 腳本執行速度測試

  前面有提到,只有找到影響速度的代碼,我們纔有可能進行優化。PEAR的benchmark包中的Benchmark_Timer類和Benchmark_Iterate類,可以用來很方便地測試腳本執行的速度。(關於PEAR的安裝與配置請自行查看相關資料) 首先用Benchmark_Iterate類來測試程序中某個函數或類的某個方法的執行時間。

benchmark1.php

<?php require_once('Benchmark/Iterate.php'); $benchmark = new Benchmark_Iterate(); $benchmark->run(10, 'myFunction','test'); $result = $benchmark->get(); echo " "; print_r($result); echo " "; exit; function myFunction($var) { // do something echo 'Hello '; } ?> 

  建立benchmark Iterate對象$benchmark,這個對象用來執行myFunction函數10次。  $argument變量每次都傳遞給myFunction. 多次運行的分析結果存入$result,然後用benchmark對象的get()方法來獲取。這個結果用print_r()輸出到屏幕。通常會輸出這樣的結果:  Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Array( [1] => 0.000427 [2] => 0.000079 [3] => 0.000072 [4] => 0.000071 [5] => 0.000076 [6] => 0.000070 [7] => 0.000073 [8] => 0.000070 [9] => 0.000074 [10] => 0.000072 [mean] => 0.000108 [iterations] => 10)  myFunction的每次執行,benchmark對象都會跟蹤執行時間。並且會計算平均的執行時間([mean]那一行)。通過多次運行目標函數,你可以得到該函數的平均運行時間。  在實際測試中,函數的次數應當至少1000次左右,這樣可以得到較客觀的結果。

  現在我們看看另一個測試腳本運行時間的方法--使用Benchmark_Timer類來測試一段代碼執行所消耗的時間及這一段代碼中每次調用與下一次調用間的時間。

benchmark2.php

<?php require_once 'Benchmark/Timer.php'; $timer = new Benchmark_Timer(); $timer->start(); $timer->setMarker('start_myFunction'); for($i=0; $i<10; $i++){ myFunction($argument); } $timer->setMarker('end_myFunction'); $timer->stop(); $profiling = $timer->getProfiling(); echo ' Time elapsed: ' . $timer->timeElapsed('start_myFunction','end_myFunction') .' '; echo ' '; print_r($profiling); echo ' '; exit; function myFunction($var) { static $counter = 0; // do something echo $counter++ . ' '; } ?> 

  首先,建立一個benchmark timer對象$timer。然後調用start()方法,表示開始計時。 SetMaker()方法用來標記要測試的代碼段。MyFunction()函數在循環中被調用,表示一段要執行的代碼(當然實際中不會這麼簡單)。然後再用$timer對象的setMarker()方法標記程序執行終點。分析信息用getProfiling()來獲取。在兩個標記間程序執行消耗的時間用timeElapsed()方法計算出來(就像例子中的循環)。最後,用print_r()輸出信息到屏幕:

Array( [0] => Array ( [name] => Start [time] => 1085730111.27175200 [diff] => - [total] => 1085730111.271752 ) [1] => Array ( [name] => start_myFunction [time] => 1085730111.27203800 [diff] => 0.000286 [total] => 1085730111.272038 ) [2] => Array ( [name] => end_myFunction [time] => 1085730111.27263200 [diff] => 0.000594 [total] => 1085730111.272632 ) [3] => Array ( [name] => Stop [time] => 1085730111.27271800 [diff] => 0.000086 [total] => 1085730111.272718 ))0 1 2 3 4 5 6 7 8 9 Time elapsed: 0.000594 

  通過這種方法,你可以在代碼中設置大量時間段標記,獲取每段代碼執行時消耗的時間,很容易可以看出到底是哪一部份的代碼影響了整個程序的運行效率。然後開始着手對這部份代碼進行改進。  用以上兩種方法,你可以找出代碼中最影響速度的部份代碼。另外還可以用來對優化後的代碼進行測試,看看到底執行速度提高了多少。通過測試->優化->測試->優化 …這樣不斷循環,你可以最終確定提供最佳效率的代碼。

<2> 加速

◆ 代碼優化

  掌握了PEAR::BenchMark,現在你已經知道如何測試你的代碼,知道如何判斷你的代碼是快是慢,是哪一部份比較慢。那麼接下來我要說的就是如何消滅或優化那部份慢的代碼。這一點上我個人最主要的經驗只有兩點,一是消除錯誤的或低效的循環;二是優化數據庫查詢語句。其實還存在一些其它的優化細節,比如“str_replace比ereg_replace快”、“echo比print快”等等。這些我暫時都放在一邊,稍後我會提到用緩存來對付過於頻繁的IO。下面我們將三個功能相同,但程序寫法不同的函數的效率(消耗的時間)進行對比。

badloops.php

 <?php require_once('Benchmark/Iterate.php'); define('MAX_RUN',100); $data = array(1, 2, 3, 4, 5); doBenchmark('v1', $data); doBenchmark('v2', $data); doBenchmark('v3', $data); function doBenchmark($functionName = null, $arr = null) { reset($arr); $benchmark = new Benchmark_Iterate; $benchmark->run(MAX_RUN, $functionName, $arr); $result = $benchmark->get(); echo '<br>'; printf("%s ran %d times where average exec time %.5f ms",$functionName,$result['iterations'],$result['mean'] * 1000); } function v1($myArray = null) { // 效率很差的循環 for ($i =0; $i < sizeof($myArray); $i++) { echo '<!--' . $myArray[$i] . ' --> '; } } function v2($myArray = null) { // 效率略有提高 $max = sizeof($myArray); for ($i =0; $i < $max ; $i++) { echo '<!--' . $myArray[$i] . ' --> '; } } function v3($myArray = null){ //最佳效率 echo "<!--", implode(" --> <!--", $myArray), " --> "; } ?> 

  程序輸出的結果大概是這樣的:

v1 ran 100 times where average exec time 0.18400 ms v2 ran 100 times where average exec time 0.15500 ms v3 ran 100 times where average exec time 0.09100 ms

  可以看到,函數的執行時間變少,效率上升。  函數v1有個很明顯的錯誤,每一次循環的時間,都需要調用sizeof()函數來計算。函數v2則在循環外把$myArray數組的元素個數存到$max變量中,避免了每次循環都要計算數組的元素個數,所以效率提高了。函數v3的效率最高,利用了現成的函數,避免循環。  這個例子只是給你一個感性的認識,明白什麼是相對高效的代碼。在實際開發中,我相信會有很多人會迷迷糊糊地寫出很多低效率的代碼。要把代碼寫得精煉而高效,恐怕需要時間去錘鍊:-) 但這是另一個話題了,我們略過不談。  數據庫應用基本上每個PHP程序都會用到,在實際開發中我發現最影響整個系統效率的就是數據庫這部份。至於數據庫的優化和數據查詢語句的優化,在此限於篇幅不詳細討論。你可以參看這兩篇文章: http://www.phpe.net/articles/340.shtml http://www.phpe.net/articles/323.shtml   及這篇討論: http://club.phpe.net/index.php?s=&act=ST&f=15&t=4783&st=0(前面幾篇貼子總結不錯),主要是針對MySQL的。

◆壓縮輸出 gzip

  利用Apache中的mod_gzip模塊,我們可以利用gzip的壓縮算法來對Apache服務器發佈的網頁內容進行壓縮後再傳輸到客戶端的瀏覽器。如果是純文本的內容,效果非常明顯,大約可以壓縮到原來的30%-40%,使用戶的瀏覽速度大大加快。  Gzip需要客戶端瀏覽器支持,目前大部份瀏覽器都支持gzip,如IE,Netscape,Mozilla等,所以這種方法值得一試。我們可以利用PHP中的預定義變量$_SERVER[‘HTTP_ACCEPT_ENCODING’]來判斷客戶端瀏覽器是否支持gzip。

gzip1.php

<?php if(ereg('gzip',
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章