2017年1月12日 星期四

Hostinger 主機運算資源限制與 PHP 效能優化

前天打開我的 Hostinger 網頁時發現如下畫面 :


之前也偶而出現過, 只要過一會兒就消失了, 但這回卻很久一段時間都如此, 所以我發了一封信去問客服, 答覆如下 :

Hello Tony, Your website exceeded the number of allowed processes: "user: u125672222 (125672222) Limits reached - numproc: 27/20". We use the 'CloudLinux' system to ensure that no single hosting account can monopolize server resources to the detriment of any other. If your site is consistently hitting its resource limits, we recommend you to consider optimizing your website so it would not use so many resources. However, if you need more server resources please consider upgrading your plan to "Business" or "VPS" plan to avoid such problems in the future. Regards, Catalin R. Customer Success team http://www.hostinger.co.uk

甚麼是 CloudLinux 呢? 我查 Google 找到這位威廉獅的文章 :

# 不要用Serverzoo 提供的CloudLinux 的五大原因 Linode 強大VPS 資源為你解密
# CloudLinux 的中文說明,解釋,使用和初步認知教學

他介紹的是日本虛擬主機商 Linode.com 提供的進階的 CPanel 管理功能 (貴啊), 可以看到網站的資源耗用情形. 但是我問了 Hostinger 客服, 他們沒有提供這些功能, 所以無法得知耗用狀態. 難道真要再花錢升級到 Business 或 VPS 方案嗎?

昨天用 Live Chat 跟 Hostinger 的客服 Philip 請教了關於 Resource Limit 的問題, 他說 Premium 與 Business 的 hosting plan 都有運算資源限制, 只有升級為 VPS 才不受限制 (貴啊). 他們沒有如 linode.com 那樣的資源檢視網頁, 只提供 phpinfo 頁面, 但一大堆表格我根本不知道哪個才是跟 Resource 相關的, Philip 因此截了一張圖給我 :


原來就是在 Core 項目下面.

沒辦法, 只好照 Hostinger 客服建議回頭檢視自己的程式碼, 看看是否有哪些地方可以改善. 但我寫程式向來功能優先, 效能其次, 所以從沒在管甚麼優化不優化的. 但現在火燒屁股, 不管也不行了. 於是找到了 Tsung's Blog 的這篇文章 :

PHP 程式效能優化的 40 條建議

裡面洋洋灑灑 40 條準則令人眼花撩亂, 其中有 8 條是比較常見, 而且規則明確可立即改正的, 摘要轉載如下 :
  1. 使用 echo 的多重參數(譯註:指用逗號而不是句點)代替字元串連接 (3)
  2. 註銷那些不用的變數尤其是大陣列,以便釋放內存 (5)
  3. require_once() 代價昂貴 (7)
  4. str_replace 函數比 preg_replace 函數快,但strtr函數的效率是 str_replace 函數的四倍 (11)
  5. 資料庫連接當使用完畢時應關掉 (16)
  6. 盡量不要在 for 循環中使用函數,比如for ($x=0; $x < count($array); $x) 每循環一次都會調用 count() 函數 (19)
  7. 用單引號代替雙引號來包含字元串,這樣做會更快一些。因為 PHP 會在雙引號包圍的字元串中搜尋變數,單引號則不會。當然,只有當你不需要在字元串中包含變數時才可以這麼做 (28)
  8. 輸出多個字元串時,用逗號代替句點來分隔字元串,速度更快。註意:只有 echo 能這麼做,它是一種可以把多個字元串當作參數的“函數”(譯註:PHP 手冊中說 echo 是語言結構,不是真正的函數,故把函數加上了雙引號) (29)
其中第 6 條 (原文第 19 條) 關於在迴圈中呼叫 count() 或 sizeof() 函數以取得陣列長度的作法, 正是我常犯的毛病, 檢視專案原始碼, 每一支程式都違反這條規則, 例如 :

$SQL="SELECT * FROM `stocks_list` ";
$RS=run_sql($SQL);
for ($i=0; $i<count($RS); $i++) {
  .....
  }

像這樣如果有 1000 筆紀錄, 則 count() 函數會被呼叫 1000 次, 造成無謂的資源耗用 (記憶體, 執行時間), 應該改成下面做法 :

$SQL="SELECT * FROM `stocks_list` ";
$RS=run_sql($SQL);
$cnt=count($RS);
for ($i=0; $i<$cnt; $i++) {
  .....
  }

這樣 count() 函數只被呼叫一次而已.

其次是第 2 條 (原文第 5 條), 占用大量記憶體的變數不用時就該設為 null 以便盡速釋出記憶體, 特別是陣列, 儲存大量文字的變數, 例如檔案讀取函數讀進來的資料, 或者利用 cURL 函數從網路中下載的網頁檔.

最後是第 3 條 (原文第 7 條), 盡量用 include 與 require 取代 include_once  與 require_once, 因為後者在程式執行時要額外花時間與資源去檢查是否有重複載入情形, 會拖慢效能. 參考 :

include 與 require 的差別
[PHP]require 和 include
談php的include和require

上面列舉的這三項我都違反, 特別是前兩項我認為影響效能與資源耗用甚鉅, 所以這兩天就地毯式地把專案中的每一支程式全部爬梳一遍, 上傳伺服器更新後似乎有效, 至少今天比較少看到 Resource Limit Exceeded 回應了.

關於 PHP 程式碼優化, 下面這網頁列出的 15 的準則更明確, 尤其是資料庫連線用完就要關掉, 但是以前書上卻說程式執行完會自動關掉, 所以打算修改 mysql.php 函式庫, 在每個資料庫操作結尾處加入 mysqli_close($conn) 指令.

# 15 Tips to Optimize Your PHP Script for Better Performance for Developers (15 個優化原則)

其他參考 :

# What are some good PHP performance tips?
Hacking with PHP

2017-01-14 補充 :

今天依照上面 15 個優化準則的第 10 條 (Close the connection), 在資料庫存取函式庫 musqli.php 中添加 mysqli_close($conn) 指令, 每次存取資料庫完畢時都將連線關閉, 以節省記憶體.

準則 2 建議字串應該使用單引號, 避免使用雙引號, 因為使用雙引號時 PHP 解譯器必須花時間去檢查裡面是否有夾帶變數, 而單引號不會. 如果需要嵌入變數就用點符號串接.

準則 3 建議以 "===" 取代 "==" 進行等值比對, 因為前者檢驗條件較嚴格 (包含型別), 因此速度較快.

準則 4 建議能用 strtr() 達成功能要求的話就不要用 str_replace() 或 preg_replace(), 因為 strtr() 速度比 str_replace() 快四倍, 更別說使用正規運算式的 preg_replace() 了. 不過下面這篇文章經過實測發現, str_replace() 還是字串取代的首選 :

# php:strtr, str_replace和preg_replace的效率对比
# 比较strtr, str_replace和preg_replace三个函数的效率_php技巧
# php字符串替换函数str_replace速度比preg_replace快

最後一個原則 (Use isset) 也很有用, 就是在判斷是否為空字串時會用 strlen(), 在判斷陣列是否為空, 或函數或資料庫存取是否有傳回值時, 我們一般會用 count(), sizeof() 等函數, 但這些函數需要額外運算, 比較耗費資源, 應該改用 isset() 函數. 當然若需要取得資料長度時, isset() 就派不上用場了.

沒有留言 :