經過測試,require_once是一個性能低下的語法結構,當然,這個性能低下是相對於require而言的,本文闡述我們項目目前使用的require方式,通過實驗代碼證明其高效性,同時,描述我們在使用過程中遇到的問題,避免他人在同一個石頭上絆倒。
上面就是兩者的區別。可以看出,兩者的不同僅在於require_once有一個判斷是否已經引用過的機制。通過網絡搜索,可以看到很多關於require_once性能比require低很多的數據,這裡就不再做這個試驗。
我們項目中的做法是: 在每個文件起始位置定義一個全局變量,require的時候,使用isset($xxxxxx) or require 'xxxxx.php';
這種做法有什麼不足呢?
全局變量以$xxx方式定義的時候,如果該文件在函數內被require,該變量會被解析為函數的局部變量,而不是全局的,因此,函數內部的isset($xxx) or require 'xxx.php'這個語法結構會失效,帶來的結果當然是意料不到的,比如,類的重定義,方法的重定義等等。
前車之鑒,所以,全局變量的定義,請使用$GLOBALS['xxx'],require的時候,使用isset($GLOBALS['xxx']) or require 'xxx.php';,使用GLOBALS會比直接定義稍慢,但總比錯是要好很多的。
由於我們之前的全局變量是直接定義的,今天在和同事討論的過程中,想到另外一種寫法:
定義的位置仍然使用$xxx方式直接定義,require的方法中進行修改(文件頭部定義的全局變量和文件名是有關聯的)。
function ud_require($xxx) { global $$xxx; isset($$xxx) or require $xxx . '.php'; }
這種方式使用了動態變量,經過和直接的GLOBALS方式比較,有兩個顯著缺點:
好了,下面是我對GLOBALS方式的require和require_once的測試:
require_requireonce.php
<?php function test1($filename) { //pathinfo($filename); isset($filename) or require $filename; } function test2() { require_once 'require_requireonce_requireonce.php'; } $start = microtime(true); while($i ++ < 1000000) isset($GLOBALS['require_requireonce_require.php']) or require 'require_requireonce_require.php'; $end = microtime(true); echo "不使用方法的isset or require方式: " . ($end - $start) . "<br />/n"; $start = microtime(true); while($j ++ < 1000000) test1('require_requireonce_require.php'); $end = microtime(true); echo "使用方法的isset or require方式: " . ($end - $start) . "<br />/n"; $start = microtime(true); while($k ++ < 1000000) test2(); $end = microtime(true); echo "require_once方式: " . ($end - $start) . "<br />/n"; ?>
require_requireonce_require.php (用於測試require的被引入文件)
<?php $GLOBALS['require_requireonce_require.php'] = 1; class T1 {} ?>
require_requireonce_requireonce.php (用於測試require_once的被引入文件)
<?php class T2 {} ?>
下面是測試的結果(單位: 秒):
可以看出,不套一個方法的require速度是比使用方法的略快的,兩者速度都是require_once的10倍左右。
那麼,性能損耗究竟在哪裡呢?
上面require_requireone.php文件中的test1方法中,我注釋了一句pathinfo($filename),因為,我本來意圖是使用文件名不帶後綴作為標記性的全局變量名的,但是,當我使用pathinfo之後,我發現這種方式的性能消耗和require_once基本一致了。因此,我在那裡單獨的加了一個pathinfo的調用,又做了測試,果然是pathinfo在搗鬼。所以,後面我就修改為了現在的版本,直接使用文件名作為變量名,如果你害怕文件名重復,那不妨加上路徑名...
猜測: 加上pathinfo之後,require和require_once的性能消耗基本一致,那我們是否可以猜測PHP內部對require_once的處理是基於它的呢?據說PHP5.3中對require_once做了顯著的優化,但是,我測試過程中使用的是PHP5.3.5版本,仍然能夠看到和require明顯的差距,難道只是比之前版本較大優化?這個倒還沒有測試....
嘗試把test1方法做了如下修改:isset($GLOBALS[substr($filename, 0, strlen($filename) - 4)]) or require $filename;
使用手動的字符串截取,當然,截取是要耗時的,不過比pathinfo的版本是要好一點的。這次的測試結果是:
對於require_once修改為isset or require方式,需要注意以下幾方面:
function ud_require_once($filename) { isset($GLOBALS[$filename]) or require $filename; }