PHP中require PK require_once, 构建自己的高效require_once(10倍的性能提升)

goosman 2011-02-15 02:06:43
author: selfimpr
blog: http://blog.csdn.net/lgg201
mail: lgg860911@yahoo.com.cn
本文博客地址: http://blog.csdn.net/lgg201/archive/2011/02/14/6184745.aspx

经过测试, require_once是一个性能低下的语法结构, 当然, 这个性能低下是相对于require而言的, 本文阐述我们项目目前使用的require方式, 通过实验代码证明其高效性, 同时, 描述我们在使用过程中遇到的问题, 避免他人在同一个石头上绊倒....
如果有更好的建议和本文有不正确观点, 还望指正, 谢谢.
require: 引入一个文件, 运行时编译引入.
require_once: 功能等同于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方式比较, 有两个显著缺点:
1. 性能, 由于动态变量的引入, 比GLOBALS方式慢2倍左右
2. 无法解决间接引用问题, 因为我们无法预知被间接引用的文件名, 也就无法用global去声明那些被间接引用的文件中定义的标记性全局变量了.
好了....下面是我对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";
?>
<meta http-equiv="Content-Type: text/html; charset=utf-8" />


require_requireonce_require.php (用于测试require的被引入文件)
<?php  
$GLOBALS['require_requireonce_require.php'] = 1;
class T1 {}
?>


require_requireonce_requireonce.php (用于测试require_once的被引入文件)
<?php  
class T2 {}
?>


下面是测试的结果(单位: 秒):
不使用方法的isset or require方式: 0.22953701019287
使用方法的isset or require方式: 0.23866105079651
require_once方式: 2.3119640350342
可以看出, 不套一个方法的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的版本是要好一点的. 这次的测试结果是:
不使用方法的isset or require方式: 0.21035599708557
使用方法的isset or require方式: 0.92985796928406
require_once方式: 2.3799331188202
好了, 不再说废话了, 结论:
对于require_once修改为isset or require方式, 需要注意以下几方面:
1. 每个文件头部定义唯一的一个标记性变量, 使用$GLOBALS['XXX'] = 1;的方式定义, 并且, 建议变量名是文件名或带路径的文件名(如果单独的文件名会重复)
2. 定义一个自定义require方法:
function ud_require_once($filename) {
isset($GLOBALS[$filename]) or require $filename;
}

完, 谢谢.
...全文
470 23 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
dowebs 2011-08-21
  • 打赏
  • 举报
回复
这就带来一个问题,即然如此,为什么PHP的require_once不使用楼主的方式呢?这其实与PHP搜索文件的路径有关,PHP包含文件允许相对路径,会搜索包括includes中的路径,如果按楼主这样的方式来处理require_once,在一些特定场合会出现BUG,因为虽然已包含的路径是确定的,但是未包含的文件路径是不确定的,同样的require_once('abc.php'),在第一次调用时可能是'/home/include/abc.php',但在includes变化和abc.php被删除时,再次调用时,可能调用的是'/home/abc.php',而且在不断的包括中,子目录下包括的可能和主目录包括也可能是一致或不一致的。如果require_once如果仅允许绝对路径,自然不是问题。
dowebs 2011-08-21
  • 打赏
  • 举报
回复
究其原因,可能是require_once 每次需要从include的许多路径中找到文件是否存在,再判断列表中是否存在,而楼主直接判断变量(相当于列表)中是否存在,再读取文件,所以require_once中,使用绝对路径会稍快个1秒,但仍然不可避免的由实体文件是否存在的判断时间引起的性能问题。
goosman 2011-02-23
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 zgycsmb 的回复:]

一般用require_once
[/Quote]

恩,如果require_once引起的性能问题不会成为关注点,那自然是不用考虑的...
zgycsmb 2011-02-22
  • 打赏
  • 举报
回复
一般用require_once
goosman 2011-02-17
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 fxs_2008 的回复:]

require_once(每次引入时都要查一遍,所以慢一点),但有好处,一是不会再次引入;二是如果不存在,会报致命错!这很容易查错!

PHP code

//用global可以用,,它是不安全的

//单例
function ud_require_once($filename) {
static $inc =array();

if(!isset($inc[$filename])……
[/Quote]

首先, 目前的ud_require_once是经过测试的, 我的环境是ubuntu 10.10, php 5.3.5, apc操作码缓存开启, 使用的sapi是php-fpm, web服务器是nginx

测试结果是ud_require_once比原生require_once快10倍左右

如果给现在的ud_require_once中增加一个pathinfo的调用, 则性能回落到和原生require_once一样的水平


另外一点, 你提到的单例引用只在没有php加速时才有意义, 这一点我不同意的, php的操作码缓存加速是将一个文件编译器的分析结果缓存起来, 未来引入的时候会快, 这个缓存是在共享内存的, 和我们这里的require_once是没有关系的, 因为无论是原生的require_once还是我这里自定义的ud_require_once, 其实对文件的实际引入都是只有一次的, 所以, php加速不会对这个测试结果产生影响(另外, 我的测试是在开启缓存的情况下测试的), 而ud_require_once和原生require_once之间的差别在于对"是否已经加载过"的判断机制上面.

当然, 也并不是说这里的ud_require_once()就是万能解药, 只是总结分享出来, 给可用愿用之人使用, 让高人牛人指正...

谢谢
fxs_2008 2011-02-17
  • 打赏
  • 举报
回复
require_once(每次引入时都要查一遍,所以慢一点),但有好处,一是不会再次引入;二是如果不存在,会报致命错!这很容易查错!


//用global可以用,,它是不安全的

//单例
function ud_require_once($filename) {
static $inc =array();

if(!isset($inc[$filename])){
require $filename;
$inc[$filename]=1;
}
}



缺点:如果引用中有变量,会变成局部变量,函数作用域!所以只用来引入类!

楼主可以测一下效率!

个人估计和requirer_once的效果差不多!所以意义不大!

当然,如果你的加载是完全可控的,可以用以上的函数; 如果你不知道,requirer_once仍是很好的!我不用管以前有没有人include过!

最后一个问题,单例引用只在没有php加速时才有意义!




github 2011-02-17
  • 打赏
  • 举报
回复
junchen168168 2011-02-16
  • 打赏
  • 举报
回复
感谢楼主对二者差别的研究!
mumubangditu 2011-02-16
  • 打赏
  • 举报
回复
好文章。以前一直认为2者差异不大,但是看了测评之后,对2者又有了新的认识。
goosman 2011-02-16
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 dingsongtao 的回复:]

PHP code

function ud_require_once($filename) {
if(isset($GLOBALS[$filename])){
$GLOBALS[$filename]=1;
require $filename;
}
}


这样就不必在每个文件前加了
[/Quote]

赞..
Dleno 2011-02-16
  • 打赏
  • 举报
回复

function ud_require_once($filename) {
if(isset($GLOBALS[$filename])){
$GLOBALS[$filename]=1;
require $filename;
}
}

这样就不必在每个文件前加了
ihefe 2011-02-15
  • 打赏
  • 举报
回复
这些都没注意到,感谢分享
goosman 2011-02-15
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 leo_raphael 的回复:]

额..来散分的么...

上次在你博客中看到个关于memchache和Apc的性能测试就受益匪浅。这次这个自定义的包含语句,又让我学到不少。。

受教了。
[/Quote]

呵呵, 那个memcache和apc的性能测试多亏论坛一位大侠纠正啊...
叫我陌北就好 2011-02-15
  • 打赏
  • 举报
回复
额..来散分的么...

上次在你博客中看到个关于memchache和Apc的性能测试就受益匪浅。这次这个自定义的包含语句,又让我学到不少。。

受教了。

21,893

社区成员

发帖
与我相关
我的任务
社区描述
从PHP安装配置,PHP入门,PHP基础到PHP应用
社区管理员
  • 基础编程社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧