PHP程式應該減少brk呼叫,否則效能會受影響

weixin_34116110發表於2018-12-21

昨天工作上遇到一個非常有意思的問題,特此分享給大家,也給大家提個醒,在 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 變數的記憶體越來越大,執行速度也越來越慢,而且執行到一定時間,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程式碼的執行邏輯,從而提升效能。

相關文章