l'essentiel est invisible pour les yeux

Thursday, August 31, 2006

PHPのevalは遅くない (require vs eval)

今日の昼飯は、とんかつ 武信分点行ってきました。
ロースカツ膳 竹を食べて、かにクリームコロッケ単品を一つで注文。値段は、1600円ほど。
米油であげているからか、ジューシュにかりっとあがっていて、食べた後も意外とあっさり。肉厚でなかなか美味でした。

夜は焼酎と日本酒を取り揃えているみたいなので、焼酎を飲みながら特選醤油ダレとすだちでとんかつを食べてみたい。


最近、理由あってRuby, PHP, Javascript3つを同時に書かなければいけなくて頭が混乱しそう。

PHPのスクリプトをWEBサーバ経由で返すサーバからPHPファイルを取得し、キャッシュし(ファイルを作成して)実行するプログラムを書いてると、ふと気になった。

* ファイル作ってファイルシステム経由で読み込むよりも共有メモリでやるほうが断然早いよな。
* 共有メモリ上のデータをevalするほうが遅いか?ファイルシステムから読み込む方が遅いか?

ファイルシステムとか使うよりもダイナミックにロードする方がカッコイイが速度が気になる。。

で、作った。

SPEC
* eAcceleratorが入っている場合には共有メモリつかいますよ。
* 入っていない場合には、ファイルシステム使いますよ。
* 作成途中のファイルにアクセスが来ても不整合なファイルにアクセスすることが無いように、ロックかけるか作成してからリネームしましょう。
* ソースコード中の最初にある定数宣言でスクリプトレポジトリを設定してくださいよ。

ソースコード


// ScriptLoaderに関する設定
define('SC_UPDATE_SERVER_PATH', 'http://yourserver/');
define('SC_CACHE_DIR', 'caches/');
define('SC_CACHE_EXPIRED_MINUTES', 10);

/**
* ネットワーク越しにスクリプトをロードしキャッシュする
*/
class ScriptCache
{
/* 強制的にネットワークから読み込む */
var $_forceLoad = false;

function ScriptCache()
{
$this->_enableEAccelerator = ! is_callable("eaccelerator_load");
}

/**
* ファイルを読み込む
* キャッシュが有効な場合はキャッシュを読み込む
*
* @access public
* @param $file string 読み込むファイル名
*/
function fetch($file, $force_load=false)
{
$cach_file_name = $this->_getCachedFileName($file);
if($this->_enableEAccelerator) {
$cached_contents = eaccelerator_get($file);
if(!$cached_contents || $force_load) $this->_load($file);
eval($cached_contents);
} else {
if($force_load || !file_exists($cach_file_name) || (mktime() - filemtime($cach_file_name) > SC_CACHE_EXPIRED_MINUTES * 60)) $this->_load($file);
require_once $cach_file_name;
}
}

/**
* キャッシュファイルを削除する
*
* @access public
* @param $file string ファイル名
*/
function clear($file)
{
$cach_file_name = $this->_getCachedFileName($file);
if($this->_enableEAccelerator) {
eaccelerator_rm($file);
} else {
file_exists($cach_file_name) && unlink($cach_file_name);
}
}

/**
* ガベージコレクション
* 不要となった共有メモリ領域の開放
*/
function gc()
{
if($this->_enableEAccelerator) eaccelerator_gc();
}

/**
* ネットワーク経由でファイルを読み込みキャッシュする。
*
* @param $file string ファイル名
* @return boolean 失敗したらfalse
*/
function _load($file)
{
if($res = $this->_networkFetch(SC_UPDATE_SERVER_PATH. $file)) {
$tmp_filename = tempnam('','');
$cache_file_name = $this->_getCachedFileName($file);
if($this->_enableEAccelerator) {
eaccelerator_lock($file);
eaccelerator_put($file, preg_replace('/<\?(php:?)?|\?>/ims', '', $res['body']), SC_CACHE_EXPIRED_MINUTES * 60);
eaccelerator_unlock($file);
} else {
if($fp = fopen($tmp_filename, 'w')) {
if(fwrite($fp, $res['body']) === FALSE) {
return false;
}
fclose($fp);
rename($tmp_filename, $cache_file_name);
chmod($cache_file_name, 0644);
}
}
return true;
} else {
return false;
}
}

/**
* キャッシュファイル名の取得
*
* @access private
* @param $file string ファイル名
* @return string キャッシュファイル名
*/
function _getCachedFileName($file)
{
$cwd = getcwd();
return $cwd. '/'. SC_CACHE_DIR. $file;
}

function _networkFetch($url, $initheaders=array(), $timeout=10, $limit=10)
{
!is_array($url) && ($aUrl = parse_url($url));
$client =& new HTTP_Client();
$client->setDefaultHeader($initheaders);
$client->setMaxRedirects($limit);
$client->get($url, null, false);
if(PEAR::isError($res = $client->currentResponse())) {
return false;
}
return $res;
}
}
?>

で早速ベンチとってみた。

キャッシュせずに常にネットワーク上から読み込む。

ソースコード

start();
$sl->fetch('test.php', true);
$timer->stop();
ob_end_clean();

print '<pre>';
print_r($timer->getOutput());
print '</pre>';

$sl->gc();
?>

time indexex time%
Start1157032411.98688100-0.00%
Stop1157032412.023001000.036120100.00%
total-0.036120100.00%


ファイルシステムでキャッシュしてrequire
キャッシュタイプを切り替えるオプションは用意していないので、

$this->_enableEAccelerator = ! is_callable("eaccelerator_load");

のようにしてファイルキャッシュするようにする。


time indexex time%
Start1157032411.98688100-0.00%
Stop1157032412.023001000.036120100.00%
total-0.036120100.00%

で、次。



eAcceraletaor APIを使いメモリ上にキャッシュしてevalする。

time indexex time%
Start1157033202.28823800-0.00%
Stop1157033202.290016000.001778100.00%
total-0.001778100.00%

結果はファイルシステム経由で読み込んだ場合とほとんど同じ。


結論
PHPのevalは遅くない。
eAcceralerator API使ったが思ったほどパフォーマンスが出なかったのが気になる。