作者 主題: PHP 網站效能最佳化  (閱讀 16981 次)

0 會員 與 1 訪客 正在閱讀本文。

twu2

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 5409
  • 性別: 男
    • 檢視個人資料
    • http://blog.teatime.com.tw/1
PHP 網站效能最佳化
« 於: 2004-02-02 14:48 »
上個月我們公司內部的系統上線之後, 就發現原本測試的時候, 系統很正常運作, 怎麼上線之後, 變的這麼的慢.... 經過了這幾個星期的奮戰之後, 看起來似乎比之前好上許多了.

發現討論這類相關問題的網站似乎不多, 原文的就不多了, 中文的更少.
所以, 歡迎大家來提供一下各位的經驗.

* 如果可能, 把資料庫主機與網站主機分開來.
* 如果可能, 把靜態的資料 (如影像...) 放到另一台主機來存取. 與 PHP 的分開.
* 使用 SMP 的機器會比較好些.
* 網站伺服器的記憶體愈多愈好.
...

twu2

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 5409
  • 性別: 男
    • 檢視個人資料
    • http://blog.teatime.com.tw/1
PHP 網站效能最佳化
« 回覆 #1 於: 2004-02-02 14:50 »
使用 MMCache or Zend Performance Suite 之類的軟體來處理 opcode 的 cache.

這些軟體在不更改程式的情形下, 會有不小的效果.
這兩者的效能差不多, 但... 前者的開發人員被後者請去上班了, 可能短時間內沒人有能力接手修改程式.

twu2

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 5409
  • 性別: 男
    • 檢視個人資料
    • http://blog.teatime.com.tw/1
PHP 網站效能最佳化
« 回覆 #2 於: 2004-02-02 14:54 »
修改 php.ini 的 output_handler, 使用 ob_gzhandler, 這樣子會將資料使用 gzip 壓縮後再傳送. (必須 browser 有支援)

twu2

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 5409
  • 性別: 男
    • 檢視個人資料
    • http://blog.teatime.com.tw/1
PHP 網站效能最佳化
« 回覆 #3 於: 2004-02-02 15:11 »
如果網站的資料並非強調 real-time 的資料, 可以考慮使用 page cache 模組.
上述的 MMCache 之類的 opcode cache 仍會執行每一個 script. 而使用 page cache 的方式, 在一定的時間內, 就不會再次執行該 script, 而是把之前的網頁送出.

事實上, 有許多資料來說, 你送給使用者的網頁是 10 秒之前的資料, 或 1 分鐘之前的資料, 並不會有差別.

MMCache 本身也有一個 mmcache_cache_page() 可以做類似的功能, 不過... 我試的結果都會有問題. (在網頁前會送出一些字元)

所以我使用 JPCache. 相關的軟體有不少, 看你要用那一個吧.

如果是 JPCache, 只要安裝後, 設定 jpcache-config.php 之後, 就可以使用.
使用時, 在你的 php script 開頭, 加上

$cachetimeout = 50;
include "jpcache.php";

就可以了. 這個 $cachetimeout 就是整個網頁有效的秒數.

記住, 如果你的程式要檢查 session, user... 等資料, 要把這個放在檢查之後, 否則別人可能經由 cache 看到不該看的資料.

預設 cache 只有 cache GET 的參數, 並不會包含 POST 的參數.
如果參數不同, 並不會當成相同的網頁.

twu2

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 5409
  • 性別: 男
    • 檢視個人資料
    • http://blog.teatime.com.tw/1
PHP 網站效能最佳化
« 回覆 #4 於: 2004-02-02 16:59 »
其實我們主機 loading 過重的主要原因是, 我們系統中有兩個網頁是屬於 monitor 的程式, 一般來說預設的 refresh 時間是 60 秒, 用來顯示我們目前交換機上頭的狀態與登入到交換機系統的使用者的狀態.

會看這兩個畫面的使用者, 一般來說, 會有 20 - 40 個左右.

假定每次查詢會使用 1 秒 (如果沒有人時, 約 0.2 - 0.5 秒), 照說每個連上來看的人的畫面更新應該不會在同一個時間, 應該是分散的才對. 如果是完美分散的情形下, 每個  session 用一秒, 每個都不會覺得慢. 但是... 在實際上線後, 我們發現事情並不是照我們所想的樣子.

在 browser 上頭處理 refresh 時, 有分為支援 javascript 與否而使用
 meta http-equiv="Refresh"

 setTimeout() 配合 body 的 onload 事件
這兩種方式來做.

這時 browser 的 refresh 就會變成載入網頁之後才開始計算時間, 而不是由產生需求時開始計算時間. 在經過一段時間之後, 會逐漸的就把所有 session 的更新集中在同一個時間來處理.

也就是說, 假定有 40 個 session, 每次連線查詢, 如果系統不忙時每個要 1 秒, 會變成幾乎  40 個連線同時送出需求, 要求更新, 系統就會同時產生 40 個行程來處理相同的事情, 這時... 一個畫面的更新, 就可能會超過 20 秒以上才能夠完成.

就算使用上述的 MMCache 與 jpcache 來做, 一樣會造成系統的 loading 在某一段時間過重. 造成一個惡性的循環, 整個系統就幾乎時時刻刻都在負載過重的情形下運作.

我們打算利用 semaphore 來處理, 不過看了 php 網站上頭的說明, 似乎在 php 上頭, 利用 semaphore 並無法有效控制資源的使用.
所以我們改用檔案來處理.

首先, 在檢查完 session 是否有效之後, 在載入 jpcache.php 之後. 在 /tmp 下頭產生一個包含某個特殊字串 (我們用 semaphore_ 加上該 php 的檔名) 與 sessionid 的檔案. 並在整個程式結束前刪除.

然後在載入 jpcache.php 之前加上一段程式來檢查:

代碼: [選擇]

...

$dir_name = '/tmp';
$crckey = sprintf("%u", crc32(serialize($_GET)));
$sKey = 'semaphore_file_name_'.$crckey.'_';
$tmStart = time();
$last_tm = 0;
while ((time() - $tmStart) < 30) {
    list($count, $tm) = CheckSemaphoreFile($sKey, 30, $dir_name);
    if ($count < 1) break;
    if ($last_tm == 0) $last_tm = $tm;
    usleep(250);
}
if ($last_tm != 0) usleep(1000);

$cachetimeout = 30;
include "jpcache.php";

$sKeyFile = $dir_name.'/'.$sKey.$sessionid.'.key';
$fp = fopen($sKeyFile, 'w+');
if ($fp != 0) fclose($fp);

....


unlink($sKeyFile);

...

function CheckSemaphoreFile($key, $timeout = 60, $dir_name = '/tmp')
{
    $last_tm = 0;
    $now = time();
    $count = 0;
    $dir = opendir($dir_name);
    while (($file = readdir($dir)) != false) {
        if (strstr($file, $key)) {
            $sFile = $dir_name.'/'.$file;
            $tm = @filemtime($sFile);
            if (($now - $tm) > $timeout) {
                @unlink($sFile);
                continue;
            }
            if ($last_tm == 0) $last_tm = $tm;
            if ($last_tm > $tm) $last_tm = $tm;
            $count++;
        }
    }
    closedir($dir);
    return array($count, $last_tm);
}


利用上述的方法, 我們把實際去查詢的連線限制在 1 個 (上述的方法並無法保證真的在同時只會有一個連線可以執行, 可能還是有機會會有多個連線, 但機率不高).
如果你要允許同時比較多的連線, 可以把 $count 的判斷改變, 不過在實際上來說, 配合 jpcache 的使用, 同時有多個連線去查詢並不會有什麼好處.

另外, 我們也在 settimeout() 那兒的時間改為:
代碼: [選擇]

function doLoad()
{
    var nRefresh = {REFRESH};

    nRefresh = nRefresh - 10 + (20 * Math.random());
    if (nRefresh < 10) {
       nRefresh = {REFRESH};
    }
    setTimeout("refresh()", nRefresh * 1000);
}


這樣可以把每個連線更新的時間稍為的錯開.

利用上述的方法, 我們把 cache 的時間設為 30 秒, 也把更新時間設為 30 秒 (random 之後在 20 - 40 秒), 對每一個連線來說, 基本上看到的是 30 秒之內的狀態, 且系統由於只會有一個連線去查詢, 所以也很快的就可以取得資料, 如此, 整個畫面的更新, 就可以快速的取得. 系統的 loading 也大幅降低.

沒使用 MMCache 之前, load average 約在 40-50 上下.
使用 MMCache 之後, load average 約在 10-20 上下.
使用 JPCache 之後, load average 約在 8-12 之間. (會變成 loading 時高時低)
加上上述的方式之後, load average 約在 1-2 上下.

對於使用者所要看的資料來說, 並沒有很大的差異. 但是經過這樣的更改, 我們把 loading 大幅的降低.

所以.... 發生 loading 過重時, 並不是一定只能加硬體來解決, 改一下程式的寫法, 或許可以得到更大的效果.

Darkhero

  • 酷!學園 學長們
  • 俺是博士!
  • *****
  • 文章數: 3728
  • 性別: 男
    • 檢視個人資料
    • ㄚ凱隨手紀
PHP 網站效能最佳化
« 回覆 #5 於: 2004-02-05 17:02 »
真是好文阿!!!!...
推推推推推...

若是作網站管理員的實在是該看看這文章...

不知道 StudyArea 是否也要試試看大大的方法呢...
我想網站應該就會更快更穩定啦...
希望我們的討論是為了把問題解決,而不是爭論誰對誰錯.
『灌水才是重點,發文只是順便』
『我寧可讓不會釣魚的工程師餓死,也不想讓會餓死的工程師去攪沉公司....』
Blog: http://blog.darkhero.net/
秘密基地: http://www.darkhero.net/comic/
目前服務的網站: http://www.libook.com.tw/

twu2

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 5409
  • 性別: 男
    • 檢視個人資料
    • http://blog.teatime.com.tw/1
PHP 網站效能最佳化
« 回覆 #6 於: 2004-02-05 18:21 »
page cache 對於 forum 是否有效用呢?
其實是依人而異吧.

在使用 cache 之後, 會造成某個頁面 (通常是討論串), 在 cache 那段時間, 看不到新的異動. 要等到 timeout 之後, 才會讀到新的內容.

不過, 就通常而言, 一個討論串是看的人多於回覆的人, 所以設一個 1-2 分鐘(或者更長的時間), 都應該是可以接受的.

Darkhero

  • 酷!學園 學長們
  • 俺是博士!
  • *****
  • 文章數: 3728
  • 性別: 男
    • 檢視個人資料
    • ㄚ凱隨手紀
PHP 網站效能最佳化
« 回覆 #7 於: 2004-02-09 10:57 »
我是覺得應該是可以用的....畢竟討論區對資料庫存取跟php程式執行都需要挺多的效能....
Delay 一分鐘更新資料...我相信大家都可以接受....
希望我們的討論是為了把問題解決,而不是爭論誰對誰錯.
『灌水才是重點,發文只是順便』
『我寧可讓不會釣魚的工程師餓死,也不想讓會餓死的工程師去攪沉公司....』
Blog: http://blog.darkhero.net/
秘密基地: http://www.darkhero.net/comic/
目前服務的網站: http://www.libook.com.tw/

Darkhero

  • 酷!學園 學長們
  • 俺是博士!
  • *****
  • 文章數: 3728
  • 性別: 男
    • 檢視個人資料
    • ㄚ凱隨手紀
PHP 網站效能最佳化
« 回覆 #8 於: 2004-02-10 19:10 »
記的之前有跟 梁楓 提過可以安裝 mmcache 來讓網站更有效率.
不過似乎因為當時 mmcache 在Freebsd 上似乎安裝上都有些問題...

不知道目前 mmcache 是否解決這部分的問題了....

如果可以了的話...就麻煩 梁楓 囉!~...
希望我們的討論是為了把問題解決,而不是爭論誰對誰錯.
『灌水才是重點,發文只是順便』
『我寧可讓不會釣魚的工程師餓死,也不想讓會餓死的工程師去攪沉公司....』
Blog: http://blog.darkhero.net/
秘密基地: http://www.darkhero.net/comic/
目前服務的網站: http://www.libook.com.tw/

Darkhero

  • 酷!學園 學長們
  • 俺是博士!
  • *****
  • 文章數: 3728
  • 性別: 男
    • 檢視個人資料
    • ㄚ凱隨手紀
PHP 網站效能最佳化
« 回覆 #9 於: 2004-02-10 19:15 »
剛剛又發現了....

在某個討論串中.. 有提到 目前 Freebsd 的 ports 中已經包括 mmcache 了!~...

嘿嘿..梁董,來麻~~ 來麻~ ......
希望我們的討論是為了把問題解決,而不是爭論誰對誰錯.
『灌水才是重點,發文只是順便』
『我寧可讓不會釣魚的工程師餓死,也不想讓會餓死的工程師去攪沉公司....』
Blog: http://blog.darkhero.net/
秘密基地: http://www.darkhero.net/comic/
目前服務的網站: http://www.libook.com.tw/

Darkhero

  • 酷!學園 學長們
  • 俺是博士!
  • *****
  • 文章數: 3728
  • 性別: 男
    • 檢視個人資料
    • ㄚ凱隨手紀
PHP 網站效能最佳化
« 回覆 #10 於: 2004-02-10 19:40 »
忽然想到...提升網站效率的一些方法我也用到了像是利用 adodb 去取代原來的 mysql_connect 等...

由於 adodb 這個程式庫中,可以支援多樣的資料庫,且不用像是php的程式碼一樣,若是要換資料庫則需要大幅度的修改.

且 adodb 中提供了 cache 的功能.可以將資料庫的存取進行快取保存.如此可以減少資料庫的連結與資料的傳輸等的時間.
對於大量資料庫存取的網站,例如社群或是討論區等,都有很不錯的幫助.效率會有很不錯的提升,且會讓資料庫的負載降低.是一個很不錯的提升效能及降低負載的方式.
希望我們的討論是為了把問題解決,而不是爭論誰對誰錯.
『灌水才是重點,發文只是順便』
『我寧可讓不會釣魚的工程師餓死,也不想讓會餓死的工程師去攪沉公司....』
Blog: http://blog.darkhero.net/
秘密基地: http://www.darkhero.net/comic/
目前服務的網站: http://www.libook.com.tw/

damon

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 4227
    • 檢視個人資料
    • http://blog.damon.tw/
PHP 網站效能最佳化
« 回覆 #11 於: 2004-02-10 20:16 »
/usr/ports/www/turck-mmcache/

twu2

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 5409
  • 性別: 男
    • 檢視個人資料
    • http://blog.teatime.com.tw/1
PHP 網站效能最佳化
« 回覆 #12 於: 2004-02-19 13:17 »
最近發現另一個問題.

如果一個網頁產生的時間過久, 在發現 cache 逾時之後, 雖然只有一個 session 去重新產生網頁, 但是原本的作法, 會造成其他的 session 一樣停在同一個地方等待, 也就是如果產生要 10 秒,  這些等待的 session 一樣要等這麼久, 且畫面會變成空白 (在 IE 下).

所以, 就有另一個想法, 是否在逾時之後, 若已經有 session 產生新的網頁資料時, 直接傳回舊的畫面. 這樣就只會讓等待的 session 限制在產生畫面的那一個.

這個改法, 如果寫在每一個程式本身上頭, 會很不方便, 所以決定對我們目前使用的 jpcache 2 做修改.

jpcache-config.php 新增兩個變數:
代碼: [選擇]
   $JPCACHE_LOCKED_FILE = 0;
    $JPCACHE_KEY = '';


jpcache-main.php 中, 修改 jpcache_varkey() 使用非預設的 key, 另外在 jpcache_init() 檢查是否使用非預設的 key:
代碼: [選擇]

    function jpcache_init()
    {
        // Override default JPCACHE_TIME ?
        if (isset($GLOBALS["cachetimeout"]))
        {
            $GLOBALS["JPCACHE_TIME"]=$GLOBALS["cachetimeout"];
        }

        // Override default key
        if (isset($GLOBALS["cachekey"]))
        {
            $GLOBALS["JPCACHE_KEY"]=$GLOBALS["cachekey"];
        }

        // Force gzip off if gzcompress does not exist
        if (!function_exists('gzcompress'))
        {
        $GLOBALS["JPCACHE_USE_GZIP"]  = 0;
        }

        // Force cache off when POST occured when you don't want it cached
        if (!$GLOBALS["JPCACHE_POST"] && (count($_POST) > 0))
        {
            $GLOBALS["JPCACHE_ON"] = 0;
            $GLOBALS["JPCACHE_TIME"] = -1;
        }

        // A cachetimeout of -1 disables writing, only ETag and content encoding
        if ($GLOBALS["JPCACHE_TIME"] == -1)
        {
            $GLOBALS["JPCACHE_ON"] = 0;
        }

        // Output header to recognize version
        header("X-Cache: jpcache v".$GLOBALS["JPCACHE_VERSION"].
                " - ".$GLOBALS["JPCACHE_TYPE"]);
    }

    function jpcache_varkey()
    {
        $varkey = "";
        if ($GLOBALS["JPCACHE_KEY"] != '') {
            $varkey .= $GLOBALS["JPCACHE_KEY"];
        }
        else {
            if ($GLOBALS["JPCACHE_POST"])
            {
                $varkey = "POST=".serialize($_POST);
            }
            $varkey .= "GET=".serialize($_GET);
        }
        jpcache_debug("Cache varkey is set to $varkey");
        return $varkey;
    }


我們使用的 file 格式, 所以是修改 type/file.php 的內容, 在 jpcache_restore() 加上 lock 的檢查, 在 jpcache_do_end() 加上解除 lock 的檢查:

代碼: [選擇]

    function jpcache_restore()
    {
        // Construct filename
        $filename = $GLOBALS["JPCACHE_DIR"]."/".$GLOBALS["JPCACHE_FILEPREFIX"].$GLOBALS["jpcache_key"];

        // read file and unserialize the data
        $cachedata=unserialize(jpcache_fileread($filename));
        if (is_array($cachedata))
        {
            // Only read cachefiles of my version
            if ($cachedata["jpcache_version"] == $GLOBALS["JPCACHE_VERSION"])
            {
                if (($cachedata["jpcache_expire"] == "0") ||
                    ($cachedata["jpcache_expire"] >= time()))
                {
                    //Restore data
                    $GLOBALS["jpcachedata_gzdata"]   = $cachedata["jpcachedata_gzdata"];
                    $GLOBALS["jpcachedata_datasize"] = $cachedata["jpcachedata_datasize"];
                    $GLOBALS["jpcachedata_datacrc"]  = $cachedata["jpcachedata_datacrc"];
                    return TRUE;
                }
                else
                {
                    $lck_filename = $filename.'.lck';
                    $fpt = @fopen($lck_filename, "wb");
                    if ($fpt != 0) {
                        jpcache_debug("Trying lock $lck_filename");
                        if (@flock($fpt, LOCK_EX | LOCK_NB) == false) {
                            jpcache_debug("Lock $lck_filename failed!");
                            // failed to lock, someone already runinng for the same page
                            fclose($fpt);
                            //Restore data
                            $GLOBALS["jpcachedata_gzdata"]   = $cachedata["jpcachedata_gzdata"];
                            $GLOBALS["jpcachedata_datasize"] = $cachedata["jpcachedata_datasize"];
                            $GLOBALS["jpcachedata_datacrc"]  = $cachedata["jpcachedata_datacrc"];
                            return TRUE;
                        }
                        jpcache_debug("Lock $lck_filename successful!");
                        $GLOBALS["JPCACHE_LOCKED_FILE"] = $fpt;
                    }
                    jpcache_debug("Data in cachefile $filename has expired");
                }
            }
            else
            {
                // Invalid version of cache-file
                jpcache_debug("Invalid version of cache-file $filename");
            }
        }
        else
        {
            // Invalid cache-file
            jpcache_debug("Invalid content of cache-file $filename");
        }

        return FALSE;
    }

    function jpcache_do_end()
    {
        // Add additional code you might require
        $fpt = $GLOBALS["JPCACHE_LOCKED_FILE"];
        if ($fpt != 0) {
            jpcache_debug("unlock lock file!");
            @flock($fpt, LOCK_UN);
            fclose($fpt);
            $GLOBALS["JPCACHE_LOCKED_FILE"] = 0;
        }
    }


上述的修改, 目前看起來在我們的系統上頭運作很正常.

這樣子修改之後, 也就不需要之前文章所說的 CheckSemaphoreFile() 處理, 直接使用 jpcache 就可以了.

twu2

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 5409
  • 性別: 男
    • 檢視個人資料
    • http://blog.teatime.com.tw/1
PHP 網站效能最佳化
« 回覆 #13 於: 2004-02-20 18:35 »
前面使用 flock 方法有些奇怪的問題產生, 所以改了一下, 使用 fopen(filename, "xb") 的方式來確定是否為唯一取得執行者.

PS. 'x' 在 php 4.3 之後的版本才能使用.

jpcache-config.php
代碼: [選擇]

    $JPCACHE_KEY = '';
    $JPCACHE_ALLOW_EXPIRE_TIME = 0;
    $JPCACHE_DO_DELETE_LOCK = 0;


jpcache-main.php
代碼: [選擇]

    function jpcache_varkey()
    {
        $varkey = "";
        if ($GLOBALS["JPCACHE_KEY"] != '') {
            $varkey .= $GLOBALS["JPCACHE_KEY"];
        }
        else {
            if ($GLOBALS["JPCACHE_POST"])
            {
                $varkey = "POST=".serialize($_POST);
            }
            $varkey .= "GET=".serialize($_GET);
        }
//        jpcache_debug("Cache varkey is set to $varkey");
        return $varkey;
    }

    function jpcache_init()
    {
        // Override default JPCACHE_TIME ?
        if (isset($GLOBALS["cachetimeout"]))
        {
            $GLOBALS["JPCACHE_TIME"]=$GLOBALS["cachetimeout"];
        }

        // Override default JPCACHE_ALLOW_EXPIRE_TIME ?
        if (isset($GLOBALS["cacheexpiretime"]))
        {
            $GLOBALS["JPCACHE_ALLOW_EXPIRE_TIME"]=$GLOBALS["cacheexpiretime"];
        }

        // Override default key
        if (isset($GLOBALS["cachekey"]))
        {
            $GLOBALS["JPCACHE_KEY"]=$GLOBALS["cachekey"];
        }

        // Force gzip off if gzcompress does not exist
        if (!function_exists('gzcompress'))
        {
        $GLOBALS["JPCACHE_USE_GZIP"]  = 0;
        }

        // Force cache off when POST occured when you don't want it cached
        if (!$GLOBALS["JPCACHE_POST"] && (count($_POST) > 0))
        {
            $GLOBALS["JPCACHE_ON"] = 0;
            $GLOBALS["JPCACHE_TIME"] = -1;
        }

        // A cachetimeout of -1 disables writing, only ETag and content encoding
        if ($GLOBALS["JPCACHE_TIME"] == -1)
        {
            $GLOBALS["JPCACHE_ON"] = 0;
        }

        // Output header to recognize version
        header("X-Cache: jpcache v".$GLOBALS["JPCACHE_VERSION"].
                " - ".$GLOBALS["JPCACHE_TYPE"]);
    }


type/file.php
代碼: [選擇]

    function jpcache_restore()
    {
        // Construct filename
        $filename = $GLOBALS["JPCACHE_DIR"]."/".$GLOBALS["JPCACHE_FILEPREFIX"].$GLOBALS["jpcache_key"];

        // read file and unserialize the data
        $cachedata=unserialize(jpcache_fileread($filename));
        if (is_array($cachedata))
        {
            // Only read cachefiles of my version
            if ($cachedata["jpcache_version"] == $GLOBALS["JPCACHE_VERSION"])
            {
                if (($cachedata["jpcache_expire"] == "0") ||
                    ($cachedata["jpcache_expire"] >= time()))
                {
                    //Restore data
                    $GLOBALS["jpcachedata_gzdata"]   = $cachedata["jpcachedata_gzdata"];
                    $GLOBALS["jpcachedata_datasize"] = $cachedata["jpcachedata_datasize"];
                    $GLOBALS["jpcachedata_datacrc"]  = $cachedata["jpcachedata_datacrc"];
                    return TRUE;
                }
                else
                {
                    jpcache_debug("Data in cachefile $filename has expired");
                    $lck_filename = $filename.'.lck';
                    $fpt = @fopen($lck_filename, "xb");
                    if ($fpt == 0) {
                        // failed to create
                        jpcache_debug("file $lck_filename exists!");
                        // failed to create a file, it already exist, some one create it
                        // max timeout for 30 seconds
                        $max_timeout = $GLOBALS["JPCACHE_ALLOW_EXPIRE_TIME"];
                        if ($max_timeout != 0 && ((time() - $cachedata["jpcache_expire"]) < $max_timeout)) {
                            // less than max timeout
                            jpcache_debug("not expire $max_timeout, use old cache");
                            //Restore data
                            $GLOBALS["jpcachedata_gzdata"]   = $cachedata["jpcachedata_gzdata"];
                            $GLOBALS["jpcachedata_datasize"] = $cachedata["jpcachedata_datasize"];
                            $GLOBALS["jpcachedata_datacrc"]  = $cachedata["jpcachedata_datacrc"];
                            return TRUE;
                        }
                        // remove lock file
                        jpcache_debug("expire $max_timeout, delete lock file");
                    }
                    else {
                        jpcache_debug("create file $lck_filename!");
                        @fclose($fpt);
                        $GLOBALS["JPCACHE_DO_DELETE_LOCK"] = 1;
                    }
                    jpcache_debug("refresh the cache!");
                }
            }
            else
            {
                // Invalid version of cache-file
                jpcache_debug("Invalid version of cache-file $filename");
            }
        }
        else
        {
            // Invalid cache-file
            jpcache_debug("Invalid content of cache-file $filename");
        }

        return FALSE;
    }


    function jpcache_do_end()
    {
        if ($GLOBALS["JPCACHE_DO_DELETE_LOCK"] == 1) {
            // Add additional code you might require
            $lck_filename = $GLOBALS["JPCACHE_DIR"]."/".$GLOBALS["JPCACHE_FILEPREFIX"].$GLOBALS["jpcache_key"].".lck";
            @unlink($lck_filename);
        }
    }


wilson

  • 俺是博士!
  • *****
  • 文章數: 1821
  • 帥氣柴老大
    • 檢視個人資料
PHP 網站效能最佳化
« 回覆 #14 於: 2004-02-20 19:11 »
現在才看到這篇好文章~~
感謝twu2學長分享

twu2

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 5409
  • 性別: 男
    • 檢視個人資料
    • http://blog.teatime.com.tw/1
PHP 網站效能最佳化
« 回覆 #15 於: 2005-01-20 10:15 »
上來補充一下.

不知道各位有使用那一種樣板系統呢? 我們目前是使用 phplib 中的 template.inc 這個模組. (因為一開始就用了, 如果現在要換, 可能有點麻煩)

樣板好用嗎? 沒錯, 樣板的確很好用.
但是我們發現, 如果使用 template.inc 中的 block 功能時, 如果行數過多, 處理的速度會慢的嚇人.

代碼: [選擇]
<!-- BEGIN vs_row -->
  <tr>
<!-- BEGIN vs_data -->
    <td width="{VS_SIZE}%" align="{VS_ALIGN}"{VS_BGCOLOR}><font face="Arial" size="2">&nbsp;{VS_DATA}&nbsp;</font></td>
<!-- END vs_data -->
  </tr>
<!-- END vs_row -->


舉例來說, 一般我們如果要產生一個 table, 就表示這個 table 的每一行都是類似的情形, 所以很簡單的就使用像上述這樣的處理方式.
先使用一個 row block 來表示每一行的資料, 再使用一個 data block 來處理每一個欄位的資料.

所以在程式上可能會是這樣.

代碼: [選擇]
$row_name = "vs_row";
$row_var = "vs_rows";
$t->set_block("fHandle", $row_name, $row_var);
$data_name = "vs_data";
$data_var = "vs_datas";
$data_item = "VS_DATA";
$t->set_block($row_name, $data_name, $data_var);

for ($i = 0; $i < $item; $i++) {

    if (($i % 2) == 0)
        $bgcolor = '';
    else
        $bgcolor = ' bgcolor="white"';

    // time
    $t->set_var('VS_SIZE', '12');
    $t->set_var('VS_ALIGN', 'center');
    $t->set_var('VS_BGCOLOR', $bgcolor);
    $t->set_var($data_item, $data[$i]['time']);
    $t->parse($data_var, $data_name, false);

    // calls offered
    $t->set_var('VS_SIZE', '11');
    $t->set_var('VS_ALIGN', 'right');
    $t->set_var('VS_BGCOLOR', $bgcolor);
    $t->set_var($data_item, number_format($data[$i]['abncalls']+$data[$i]['acdcalls']));
    $t->parse($data_var, $data_name, true);

.....

    // maxocw
    $t->set_var('VS_SIZE', '11');
    $t->set_var('VS_ALIGN', 'right');
    $t->set_var('VS_BGCOLOR', $bgcolor);
    $t->set_var($data_item, get_time_str($data[$i]['maxocw']));
    $t->parse($data_var, $data_name, true);

    $t->parse($row_var, $row_name, true);
}


使用一個 loop 來產生所有的資料.

這樣子的處理方式, 在一般的處理上來看, 是不會有問題的, 可是, 如果你處理一個很大的 table 時, 你會發現, 產生資料的速度會隨著你的資料的增加而變慢, 而且對於一個 web 的系統來說, 這個慢的速度可能會讓人無法接受.

我們在查了一陣子之後, 才發現原來系統最主要會慢的原因就是這個. :-(

因為我們在處理這個 block 時, 你可以發現都應該只是簡單的字串轉換, 而不會有其它的運算. 所以我們決定自己寫一個小的模組, 來處理這類簡單的樣板.

代碼: [選擇]
class MyRowTemplate
{
    var $sResult = '';
    var $sOldData = '';
    var $sTempData = '';

    function MyRowTemplate($file)
    {
        $this->sOldData = file_get_contents($file);
    }

    function row_begin()
    {
        $this->sTempData = $this->sOldData;
    }

    function row_end()
    {
        $this->sResult .= $this->sTempData;
    }

    function set_var($var, $value)
    {
        $this->sTempData = str_replace('{'.$var.'}', $value, $this->sTempData);
    }

    function get_result()
    {
        return $this->sResult;
    }
}


然後把樣板拆成下面這兩個樣子.

代碼: [選擇]
{DETAIL}

代碼: [選擇]
 <tr{BGCOLOR}>
    <td width="12%" align="center"><font face="Arial" size="2">&nbsp;{TIME}&nbsp;</font></td>
    <td width="10%" align="right"><font face="Arial" size="2">&nbsp;{OFFCALLS}&nbsp;</font></td>
    <td width="10%" align="right"><font face="Arial" size="2">&nbsp;{ANSTIME}&nbsp;</font></td>
    <td width="10%" align="right"><font face="Arial" size="2">&nbsp;{ABNTIME}&nbsp;</font></td>
    <td width="10%" align="right"><font face="Arial" size="2">&nbsp;{ACDCALLS}&nbsp;</font></td>
    <td width="10%" align="right"><font face="Arial" size="2">&nbsp;{ACDTIME}&nbsp;</font></td>
    <td width="10%" align="right"><font face="Arial" size="2">&nbsp;{ABNCALLS}&nbsp;</font></td>
    <td width="9%" align="right"><font face="Arial" size="2">&nbsp;{ABNRATE}&nbsp;</font></td>
    <td width="10%" align="right"><font face="Arial" size="2">&nbsp;{IN_SL}&nbsp;</font></td>
    <td width="9%" align="right"><font face="Arial" size="2">&nbsp;{SL}&nbsp;</font></td>
  </tr>


把處理的程式改成:

代碼: [選擇]
$oRows = new MyRowTemplate('xxx.tpl');

for ($i = 0; $i < $item; $i++) {

    if (($i % 2) == 0)
        $bgcolor = '';
    else
        $bgcolor = ' bgcolor="white"';

    $oRows->row_begin();

    $oRows->set_var('BGCOLOR', $bgcolor);

    // time
    $oRows->set_var('TIME', $data[$i]['time']);

....

    // service level
    if ($data[$i]['acdcalls'] == 0)
        $oRows->set_var('SL', '100.00%');
    else
        $oRows->set_var('SL', sprintf("%.2f%%", 100 * $data[$i]['in_sl']/$data[$i]['acdcalls']));

    $oRows->row_end();

}

$t->set_var('DETAIL', $oRows->get_result());


經過類似的處理, 我們把原本產生約六百行的 table, 時間由 10 秒左右, 大幅降到 0.03 秒左右.
或許在處理上, 並沒有之前的方便, 但是, 速度的改進是十分驚人的.

我不確定其它的樣板系統 (如 FastTemplate, Smarty...), 會不會有類似的問題, 不過, 如果你使用 phplib 的 template.inc, 在你有需要產生大的 table 時, 或許可以考慮我們的處理方式.

Darkhero

  • 酷!學園 學長們
  • 俺是博士!
  • *****
  • 文章數: 3728
  • 性別: 男
    • 檢視個人資料
    • ㄚ凱隨手紀
PHP 網站效能最佳化
« 回覆 #16 於: 2005-01-23 12:53 »
Smarty 應該會快很多~...

可以參考一下小弟這次研討會的簡報之中..

Smarty 為什麼快?

    * Smarty採用類似編譯的方式產生輸出的程式.
          o Smarty 需要 templates_c 這個目錄.
          o template_c裡面的內容,看起來就像是我們一開始看到的HTML嵌入PHP程式碼.
          o 當樣版與程式碼沒有更新,則Smarty會直接輸出template_c之中的程式碼,免去再次剖析樣版的時間(光這點就比phplib之中的樣板程式快)
          o 當樣版與程式碼更新,則Smarty會聰明的更新templates_c之中相對應的檔案


Smarty 還可以更快!

    * Smarty 支援輸出的頁面進行快取(cache)
          o Smarty 可以設定 cache 這個目錄.
          o 有某些頁面,你可以好幾十分鐘再更新一次就好了......
          o Smarty 的Cache功能支援單一樣板不同內容的cache
希望我們的討論是為了把問題解決,而不是爭論誰對誰錯.
『灌水才是重點,發文只是順便』
『我寧可讓不會釣魚的工程師餓死,也不想讓會餓死的工程師去攪沉公司....』
Blog: http://blog.darkhero.net/
秘密基地: http://www.darkhero.net/comic/
目前服務的網站: http://www.libook.com.tw/

chinwork

  • 可愛的小學生
  • *
  • 文章數: 17
    • 檢視個人資料
PHP 網站效能最佳化
« 回覆 #17 於: 2006-07-17 10:50 »
請問這個問題要怎麼解決呢??


PHP Warning:  [Turck MMCache] This build of "Turck MMCache" was compiled for PHP version 4.3.4. Rebuild it for your PHP version (4.3.10) or download precompiled binaries.
 in Unknown on line 0
PHP Warning:  Unknown(): Unable to initialize module 'Turck MMCache' in Unknown on line 0

梁楓

  • 俺是博士!
  • *****
  • 文章數: 6220
    • 檢視個人資料
PHP 網站效能最佳化
« 回覆 #18 於: 2006-07-17 11:28 »
1. chinwork 你在重覆發文一次,我會將您的這篇問題全部刪除。
2. 不要拿別人的時間來換你的時間。
3. 為什麼有第二點,因為錯誤訊息很難讀嗎? 拿英文字典來翻譯一下吧?