昨天工作上遇到一個非常有意思的問題,特此分享給大家,也給大家提個醒,在 PHP 程序中盡量減少系統調用。
在我們系統中有一個 cron 腳本,完成的主要工作就是從 memcached 中獲取數據,然後同步到數據庫中。平時運行的好好的,但昨天卻遇到了問題,唯一的變化就是本次任務從 memcached 中獲取的數據非常多,總共有 100 萬條記錄。話不多少,先上偽代碼:
// 共100萬個memcached數據 $tnum = 1000000; // 共1萬個key,每個100條memcached數據 $knum = ceil($tnum/100); $mem->connect("localhost", "11211"); for ($i = 1; $i <= $knum; $i++) $k[] = $mckey."_".$i; # 一次性從 memcached 中獲取到數據 $emailmc = $mem->get($k); $email = array(); foreach ($emailmc as $v) { $s = unserialize($v); $s = explode(",", $s); # 合並數組 $email = array_merge($email, $s); } # 一次性導入到 mecached 中 importdb($email);
彪悍的 memcached
由於腳本本次運行對業務非常重要,我一直在監視,發現運行了半個小時也沒有結束,開始我思索是不是memcached一次性獲取太多了,導致memcached查詢遇到問題了?
使用 wireshark 和 strace 抓取了相關數據,發現獲取 memcached 非常快,幾秒鍾就返回了,贊一下 memcached 性能。
brk
接下去繼續分析,strace 出現了滿屏的 brk 系統調用,如下:
$ strace -p 27429 -T brk(0x6d4c000) = 0x6d4c000 <0.000007> brk(0x6d8c000) = 0x6d8c000 <0.000007> brk(0x6dcc000) = 0x6dcc000 <0.000007> brk(0x6e0c000) = 0x6e0c000 <0.000007> brk(0x6e4c000) = 0x6e4c000 <0.000006>
雖然每次的 brk 調用響應並不慢,但次數太多了,那麽到底什麽是 brk?
brk, sbrk - change data segment size
也就是說 brk 在不斷的改變某個指針對象的内容,按照上面的偽代碼,$email 變量不斷的在調整内存大小,而且 $email 變量的内存越來越大,執行速度也越來越慢,而且執行到一定時間,php出現了内存不夠的錯誤,我做了相關調整:
ini_set('memory_limit', '500M'); $email = array(); foreach ($emailmc as $v) { $s = unserialize($v); $s = explode(",", $s); $email = array_merge($email, $s); echo memory_get_usage(); }
memory_limit 是限制 php 程序能夠使用的内存大小,通過 memory_get_usage 函數發現,内存使用越來越大,雖然最後代碼也能夠運行,但卻要花費至少半個小時。
call_user_func_array
對於 php 程序來說,應用代碼是涉及不到 brk 調用的,但如果能夠減少調用,程序執行時間肯定會提高很多,現在的目的就是減少 array_merge 操作,我先修改了部分代碼,分批次從 memcached 中獲取:
// 共100萬個memcached數據 $tnum = 1000000; // 共1萬個key,每個100條memcached數據 $knum = ceil($tnum/100); $mem->connect("localhost", "11211"); $j = 1; for ($i = 1; $i <= $knum; $i++) { $k[] = $mckey."_".$i; if (count($k)>100) { $emailmc = $mem->get($k); foreach ($emailmc as $v) { $s = unserialize($v); $s = explode(",", $s); $emailarr[$j] = $s; $j++; } $k = array(); } } # 要運行 100 次 for ($i=1;$i<=$j;$i++) { $email = array_merge($email,$emailarr[$j]); } importdb($email);
我分批次從 memcached 中獲取數據,然後保存到 $emailarr 數組變量中,如果再循環 array_merge,雖然速度快了一些,但仍然要100次,運行速度仍然非常慢。
我思索是不是在 php 内部能夠將 $emailarr 數組一次性合並呢?雖然有思路,但不知道具體如何操作,諮詢了 php 大牛,提出了 call_user_func_array 函數。
修改如下:
$email = call_user_func_array('array_merge', $email); importdb($email);
代碼居然2秒就返回了,避免了由 php 應用代碼進行大量的 array_merge 合並,由 php 内部一次性完成了 array_merge。
可能有些同學說,爲啥你不能從 memcached 中獲取一部分數據就導入到數據庫中呢?主要原因是後面代碼太複雜,怕出現新的問題,所以本次的改造思路就是一次性獲取到 $email 變量對應的數據。
總結:php 應用代碼不會和系統調用直接産生聯繫,可系統調用非常昂貴,應該減少調用,所以在開發的時候,應該想象下php代碼的運行邏輯,從而提升性能。