为什么require_once很糟糕?
我所读到的关于更好的PHP编码实践的一切都不停地说因为速度而不使用require_once
。
为什么是这样?
做与require_once
相同的事情的正确/更好的方法是什么? 如果有关系,我使用PHP5。
require_once
和include_once
都要求系统logging已经包含/需要的内容。 每个*_once
调用意味着检查该日志。 所以那里肯定有一些额外的工作,但足以损害整个应用程序的速度?
…我真的怀疑它…除非你真的在旧硬件或做很多事情 。
如果你正在做数以千计的工作,你可以以更轻松的方式来完成工作。 对于简单的应用程序,只要确保只包含一次就足够了,但是如果你仍然在重新定义错误,你可能会这样:
if (!defined('MyIncludeName')) { require('MyIncludeName'); define('MyIncludeName', 1); }
我会亲自坚持*_once
声明,但愚蠢的*_once
基准,你可以看到两者之间的区别:
php hhvm if defined 0.18587779998779 0.046600103378296 require_once 1.2219581604004 3.2908599376678
require_once
要慢10-100倍,好奇的是require_once
在hhvm
看起来比较慢。 同样,如果你正在运行数千次,这只与你的代码有关。
<?php // test.php $LIMIT = 1000000; $start = microtime(true); for ($i=0; $i<$LIMIT; $i++) if (!defined('include.php')) { require('include.php'); define('include.php', 1); } $mid = microtime(true); for ($i=0; $i<$LIMIT; $i++) require_once('include.php'); $end = microtime(true); printf("if defined\t%s\nrequire_once\t%s\n", $mid-$start, $end-$mid);
<?php // include.php // do nothing.
这个post让我感到害怕,因为已经有了一个“解决办法”,而且对于所有的意图和目的来说都是错误的。 我们列举一下:
-
在PHP中定义是非常昂贵的。 您可以自己查看或者自己testing,但是在PHP中定义全局常量的唯一有效方法是通过扩展。 (类常量其实是相当不错的performance明智的,但这是一个有争议的问题,因为2)
-
如果正确使用
require_once()
,即包含类,则甚至不需要定义; 只要检查class_exists('Classname')
。 如果你所包含的文件包含代码,也就是说你正在以程序的方式使用它,绝对没有理由require_once()
应该是你需要的; 每次包含文件时,您都认为正在进行子程序调用。
所以有一段时间,很多人使用class_exists()
方法来包含它们。 我不喜欢它,因为它很糟糕,但是他们有很好的理由: require_once()
在一些更新版本的PHP之前是非常低效的。 但是这个问题已经解决了,这是我争论的一点,你必须为条件语句和额外的方法调用编写额外的字节码,远远超过了任何内部的散列表检查。
现在,承认:这个东西很难testing,因为它占用了很less的执行时间。
下面是您应该考虑的问题:作为一般规则,include包含在PHP中是很昂贵的,因为每次解释器碰到一个,它都必须切换回parsing模式,生成操作码,然后跳回去。 如果你有100 +包括,这肯定会有一个性能影响。 使用或不使用require_once的原因是一个很重要的问题,因为它使操作码caching变得困难。 这里可以find一个解释 ,但是这可以归结为:
-
如果在分析时间内,您确切知道在请求的整个生命周期中需要包含哪些文件,在最开始的时候
require()
那些文件,而操作码caching将为您处理其他所有内容。 -
如果你没有运行一个操作码caching,你是在一个困难的地方。 将所有包含内容的内容合并到一个文件中(不要在开发过程中这样做,只能在生产过程中)可以帮助parsing时间,但这是一件很痛苦的事情,同时,您也需要确切知道在请求。
-
自动加载非常方便,但速度很慢,因为自动加载逻辑必须在每次加载完成时运行。 在实践中,我发现为一个请求自动加载几个专门的文件不会导致太多的问题,但是你不应该自动加载你所需要的所有文件。
-
如果你可能有10个包含(这是信封计算的后面),所有的这种运算是不值得的:只是优化你的数据库查询或其他东西。
我很好奇,检查了Adam Backstrom与Tech Your Universe的链接。 本文介绍了使用require的原因之一,而不是require_once。 但是,他们的主张并不等于我的分析。 我很想看看我可能误解了解决办法。 我使用PHP 5.2.0进行比较。
我开始创build100个使用require_once来包含另一个头文件的头文件。 每个文件都是这样的:
<?php // /home/fbarnes/phpperf/hdr0.php require_once "../phpperf/common_hdr.php"; ?>
我使用一个快速bash破解创build这些:
for i in /home/fbarnes/phpperf/hdr{00..99}.php; do echo "<?php // $i" > $i cat helper.php >> $i; done
这样我可以轻松地使用require_once进行交换,并且在包含头文件时需要进行交换。 然后我创build了一个app.php来加载一百个文件。 这看起来像:
<?php // Load all of the php hdrs that were created previously for($i=0; $i < 100; $i++) { require_once "/home/fbarnes/phpperf/hdr$i.php"; } // Read the /proc file system to get some simple stats $pid = getmypid(); $fp = fopen("/proc/$pid/stat", "r"); $line = fread($fp, 2048); $array = split(" ", $line); // write out the statistics; on RedHat 4.5 w/ kernel 2.6.9 // 14 is user jiffies; 15 is system jiffies $cntr = 0; foreach($array as $elem) { $cntr++; echo "stat[$cntr]: $elem\n"; } fclose($fp); ?>
我将require_once头与使用头文件的require头对比如下:
<?php // /home/fbarnes/phpperf/h/hdr0.php if(!defined('CommonHdr')) { require "../phpperf/common_hdr.php"; define('CommonHdr', 1); } ?>
当require和require_once运行时,我没有发现太大的区别。 事实上,我最初的testing似乎暗示require_once稍快,但我不一定相信。 我用10000个input文件重复了这个实验。 在这里我看到了一致的差异。 我多次运行testing,结果很接近,但使用require_once平均使用30.8个用户jiffies和72.6个系统jiffies; 使用要求平均使用39.4个用户jiffies和72.0个系统jiffies。 因此,使用require_once看起来负载略低。 但是,挂钟略有增加。 10,000个require_once调用平均需要10.15秒才能完成,10,000个需求调用平均需要9.84秒。
下一步是研究这些差异。 我用strace来分析正在进行的系统调用。
从require_once打开文件之前,进行以下系统调用:
time(NULL) = 1223772434 lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0 lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=88, ...}) = 0 time(NULL) = 1223772434 open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3
这与需求相反:
time(NULL) = 1223772905 lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0 lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=146, ...}) = 0 time(NULL) = 1223772905 open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3
Tech Your Universe意味着require_once应该进行更多的lstat64调用。 但是,他们都做了相同数量的lstat64调用。 可能的差别是我没有运行APC来优化上面的代码。 然而,接下来的事情是比较整个运行的strace输出:
[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out 190709 strace_1000r.out 210707 strace_1000ro.out 401416 total
使用require_once时,实际上每个头文件约有两个系统调用。 一个区别是require_once对time()函数有额外的调用:
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out strace_1000r.out:20009 strace_1000ro.out:30008
另一个系统调用是getcwd():
[fbarnes@myhost phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out strace_1000r.out:5 strace_1000ro.out:10004
这是因为我决定在hdrXXX文件中引用相对path。 如果我把它作为绝对引用,唯一的区别是在代码中进行额外的时间(NULL)调用:
[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out 190705 strace_1000r.out 200705 strace_1000ro.out 391410 total [fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out strace_1000r.out:20008 strace_1000ro.out:30008
这似乎意味着可以通过使用绝对path而不是相对path来减less系统调用次数。 除此之外唯一的区别就是时间(NULL)调用,这些调用看起来是用来testing代码来比较更快的。
另外需要注意的是,APC优化包有一个名为“apc.include_once_override”的选项,声称它减less了require_once和include_once调用所产生的系统调用次数(请参阅PHP文档 )。
对不起,很长的职位。 我好奇。
你能否给我们提供任何指向这些编码实践的链接,以避免它? 就我而言, 这是一个完全没有问题的问题 。 我没有看过自己的源代码,但我想像include
和include_once
之间的唯一区别是, include_once
将该文件名添加到数组,并且每次都检查数组。 保存这个数组很容易,所以对它进行search应该是O(log n),即使是一个中等大小的应用程序也只会包含几十个。
更好的方法是使用面向对象的方法并使用__autoload() 。
*_once()
函数统计每个父目录,以确保您包含的文件与已经包含的文件不同。 这是经济放缓的部分原因。
我build议使用Siege这样的工具进行基准testing。 您可以尝试所有build议的方法并比较响应时间。
更多关于在Tech Your Universe上的require_once()
。
PEAR2维基(当它存在的时候)用来列举放弃所有 require / include指令的好理由 ,至less对于自动加载是至关重要的。 当像phar这样的替代包装模型即将出现时,这些将你与僵化的目录结构联系在一起 。
更新:由于wiki的Web归档版本是丑陋的,我已经复制了下面最引人注目的原因:
- 为了使用(PEAR)包,include_path是必需的。 这使得在其他应用程序中使用自己的include_path绑定一个PEAR包来创build一个包含所需类的单个文件变得很困难,从而将一个PEAR包移动到一个phar归档文件,而无需修改大量的源代码。
- 当顶级require_once与条件require_once混合在一起时,这可能会导致代码不能被诸如APC之类的操作码caching所caching,这将与PHP 6捆绑在一起。
- 相对require_once要求include_path已经被设置为正确的值,使得不能使用没有正确include_path的包
这不是使用的function是坏的。 这是一个不正确的理解如何和何时使用它,在一个整体的代码库。 我只是在可能被误解的概念上添加更多的上下文:
人们不应该认为require_once是一个慢函数。 你必须以某种方式包含你的代码。 require_once()
与require()
的速度不是问题。 这是关于阻碍使用盲目性的警告的性能。 如果广泛使用而不考虑上下文,则会导致大量的内存浪费或代码浪费。
我所看到的真的很糟糕,就是当庞大的整体框架以各种错误的方式使用require_once()
时,尤其是在复杂的面向对象的环境中。
以在许多库中看到的在每个类的顶部使用require_once()
为例:
require_once("includes/usergroups.php"); require_once("includes/permissions.php"); require_once("includes/revisions.php"); class User{ //user functions }
所以User
类被devise为使用所有其他3个类。 很公平! 但现在如果访问者正在浏览网站,甚至没有login并加载框架: require_once("includes/user.php");
为每一个请求。
它包括1 + 3 不必要的类,它不会在这个特定的请求期间使用。 这是多么膨胀的框架最终使用每个请求40MB,而不是5MB或更less。
其他可被滥用的方式是当一个class被其他人重复使用时! 假设你有大约50个使用helper
函数的类。 为了确保这些类在加载时可以使用helpers
,您将得到:
require_once("includes/helpers.php"); class MyClass{ //Helper::functions();//etc.. }
这里本身没有错。 但是,如果一个页面请求碰巧包括15个类似的类。 你正在运行require_once
15次,或者是一个很好的视觉效果:
require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php"); require_once("includes/helpers.php");
使用require_once()在技术上影响运行该函数14次的性能,除此之外必须parsing这些不必要的行。 只有10个其他类似问题的高度使用的类,它可以占100多行这样的毫无意义的重复代码。
有了这个,可能值得使用require("includes/helpers.php");
在你的应用程序或框架的引导,而不是。 但是因为一切都是相对的, 这一切都取决于 helpers
类的权重和使用频率是否值得保存15-100行的require_once()
。 但是,如果在任何给定的请求中不使用helpers
文件的概率是没有的,那么require
应该在你的主类中。 单独在每个课堂上要求一次,就会浪费资源。
require_once
函数在必要的时候是有用的,但不应该被认为是一个单一的解决scheme,用来加载所有的类。
即使require_once
和include_once
比require
和include
(或者其他可能的替代方法)慢,我们在这里讨论的是微型优化的最小级别。 在优化写得不好的循环或数据库查询上花费的时间要比担心诸如require_once
类的要好得多。
现在,有人可能会说, require_once
允许编码实践不好,因为你不需要注意保持你的包装清洁和有组织,但这与function本身没有关系,尤其是速度。
显然,为了代码清洁和易于维护,自动加载更好,但我想说清楚,这与速度无关。
你testing,使用包括,OLI的替代和__autoload(); 并用安装了APC的东西进行testing。 我怀疑用恒定的东西会加快速度。
是的,它比plain ol'require()要贵一些。 我认为重点是,如果你能够保持你的代码组织得足够不包含复制,不要使用* _once()函数,因为它会为你节省一些周期。
但是使用_once()函数不会杀死你的应用程序。 基本上, 不要用它作为借口,不必组织你的包含 。 在某些情况下,使用它仍然是不可避免的,这不是什么大不了的事情。
我认为在PEAR文档中,有一个require,require_once,include和include_once的build议。 我遵循这个准则。 你的申请会更清楚。
这与速度无关。 这是关于失败优雅。
如果require_once()失败,你的脚本就完成了。 没有其他处理。 如果使用include_once(),则脚本的其余部分将尝试继续呈现,因此用户可能对脚本中失败的某些内容不太了解。
我个人认为require_once(或者include_once)的用法是不好的做法,因为require_once会检查你是否已经包含了这个文件,并且抑制包含两个文件的错误导致致命的错误(比如函数/类/等的重复声明)。 。
你应该知道你是否需要包含一个文件。