Answers:
require_once
并且include_once
都要求系统会记录本已被列入什么/需要的。每个*_once
电话都意味着检查该日志。因此,肯定有一些额外的工作要做,但足以损害整个应用程序的速度吗?
......我真的很怀疑......除非你是在真正的老硬件或做一个不少。
如果您要进行数千次操作*_once
,则可以轻松地自己完成工作。对于简单的应用程序,只是确保你只包括一次应该足够了,但如果你仍然得到重新定义错误,你可以是这样的:
if (!defined('MyIncludeName')) {
require('MyIncludeName');
define('MyIncludeName', 1);
}
我个人会坚持这样的*_once
说法,但在愚蠢的百万通过基准上,您会发现两者之间的区别:
php hhvm
if defined 0.18587779998779 0.046600103378296
require_once 1.2219581604004 3.2908599376678
慢10-100倍,require_once
奇怪的require_once
是hhvm
。同样,这仅在您运行*_once
数千次时才与代码相关。
<?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.
这个线程让我感到畏缩,因为已经有一个“解决方案发布”,而且就所有意图和目的而言都是错误的。让我们列举一下:
在PHP中,定义确实非常昂贵。您可以自己查找或测试它,但是在PHP中定义全局常量的唯一有效方法是通过扩展。(类常量实际上是相当不错的性能,但这是有争议的,因为2)
如果使用require_once()
得当,即包含类,则甚至不需要定义。只要检查是否class_exists('Classname')
。如果您要包含的文件包含代码,即您以程序方式使用它,则绝对没有理由require_once()
对您有必要。每次包含文件时,您都假定要进行子例程调用。
所以有一段时间,很多人的确使用了该class_exists()
方法。我不喜欢它,因为它很丑陋,但是他们有充分的理由:require_once()
在一些最新版本的PHP之前效率很低。但这是固定的,我的争辩是,为条件而必须编译的额外字节码和额外的方法调用将远远超过任何内部哈希表检查。
现在,承认:这些东西很难测试,因为它只占执行时间的很少。
这是您应该考虑的问题:通常,在PHP中包含昂贵的代码,因为每次解释器命中时,它都必须切换回解析模式,生成操作码,然后再跳回。如果您有100个以上的包含项,则肯定会对性能产生影响。使用或不使用require_once如此重要的问题的原因是,因为这使操作码缓存变得困难。可以在这里找到对此的解释,但是归结为:
如果在解析期间,您确切知道请求整个生命周期中需要哪些包含文件,require()
那么一开始它们和操作码缓存将为您处理其他所有文件。
如果您没有运行操作码缓存,那么您将处于困境。将所有包含内容内联到一个文件中(在开发过程中请勿执行此操作,仅在生产环境中执行此操作)当然可以帮助解析时间,但这是一件很麻烦的事,而且,您还需要确切地知道在请求。
自动加载非常方便,但是很慢,原因是每次完成包含操作时都必须运行自动加载逻辑。在实践中,我发现针对一个请求自动加载多个专用文件不会造成太大的问题,但是您不应该自动加载所需的所有文件。
如果有可能10包括(这是一个非常回信封计算),这一切手淫是不值得的:只是优化您的数据库查询或东西。
define()
,require_once()
并且defined()
在我的计算机上每个都需要1-2微秒。
我很好奇,并查看了亚当·贝克斯特伦(Adam Backstrom)与Tech Your Universe的链接。本文介绍了应使用require而不是require_once的原因之一。但是,他们的主张不符合我的分析。我很想知道我可能在哪里对解决方案进行了错误的分析。我使用PHP 5.2.0进行比较。
我首先创建了100个使用require_once的头文件来包含另一个头文件。这些文件中的每一个看起来像:
<?php
// /home/fbarnes/phpperf/hdr0.php
require_once "../phpperf/common_hdr.php";
?>
我使用快速的Bash hack创建了这些文件:
for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
echo "<?php
// $i" > $i
cat helper.php >> $i;
done
这样,当包括头文件时,我可以轻松地在require_once和require之间切换。然后,我创建了一个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 with 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 vs. require_once运行此程序时,我发现没有太大区别。实际上,我的初始测试似乎暗示require_once稍快一些,但我不一定相信。我用10000个输入文件重复了该实验。在这里,我确实看到了一致的差异。我多次运行测试,结果接近,但是使用require_once平均使用30.8个用户jiffies和72.6个系统jiffies;使用require平均使用39.4个用户jiffies和72.0个系统jiffies。因此,使用require_once似乎负载稍低。但是,挂钟时间会稍微增加。10,000个require_once调用平均花费10.15秒才能完成,而10,000个require调用平均花费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
这与require相反:
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文件中引用的相对路径。如果我将其作为绝对引用,则唯一的区别是代码中进行了额外的time(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
这似乎暗示您可以通过使用绝对路径而不是相对路径来减少系统调用的次数。唯一的区别是time(NULL)调用,该调用似乎用于检测代码以比较更快的代码。
另一个要注意的是,APC优化程序包具有一个称为“ apc.include_once_override”的选项,该选项声称它减少了require_once和include_once调用引起的系统调用次数(请参阅PHP文档)。
您能给我们提供任何指向这些编码实践的链接来避免这种情况吗?就我而言,这是一个完全没有问题的问题。我自己没有看过源代码,但是我想include
和之间的唯一区别include_once
是include_once
将文件名添加到数组中并每次检查数组。使该数组排序很容易,因此对其进行搜索应为O(log n),即使是中等规模的应用程序也只能包含几十个包含项。
做事的更好方法是使用面向对象的方法并使用__autoload()。
__autoload()
不鼓励,并可能在未来被废弃,你应该使用spl_autoload_register(...)
这些天...... PS2:不要误会我的意思,我使用的自动加载功能有时; )
它没有使用不好的功能。在整体代码库中,对如何以及何时使用它的理解是错误的。我将为可能被误解的概念添加更多上下文:
人们不应该认为require_once是一个缓慢的函数。您必须以一种或另一种方式包含您的代码。require_once()
vs. require()
的速度不是问题。这是关于性能受到阻碍的警告,可能会导致盲目使用它。如果在不考虑上下文的情况下广泛使用它,则可能导致巨大的内存浪费或浪费的代码。
我看到的确很糟糕,那就是大型的整体框架require_once()
以所有错误的方式使用时,尤其是在复杂的面向对象(OO)环境中。
以require_once()
在许多类库中看到的在每个类顶部使用的示例为例:
require_once("includes/usergroups.php");
require_once("includes/permissions.php");
require_once("includes/revisions.php");
class User{
// User functions
}
因此,User
该类旨在使用所有其他三个类。很公平!
但是现在,如果访问者正在浏览站点,甚至没有登录并且框架已加载,该怎么办:require_once("includes/user.php");
对于每个单个请求。
它包括1 + 3个不必要的类,在该特定请求期间将永远不会使用。这就是ated肿的框架最终每个请求使用40 MB而不是5 MB或更少的方式。
可以被滥用的其他方式是,当一个班级被许多其他人重新使用时!假设您有大约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次的性能。在仅有10个其他类似问题的高度使用类的情况下,它可以解释100多个这样毫无意义的重复代码行。
这样,可能值得require("includes/helpers.php");
在应用程序或框架的引导程序中使用它。但是由于所有内容都是相对的,所以这全都取决于helpers
该类的权重与使用频率是否值得保存15-100行require_once()
。但是,如果helpers
在任何给定请求中不使用文件的可能性为零,那么require
绝对应该在您的主类中。有require_once
在每个类别单独成为一种资源的浪费。
该require_once
函数在必要时很有用,但不应视为在各处用于加载所有类的整体解决方案。
PEAR2 Wiki(存在时)用于列出放弃所有 require / include指令以支持自动加载(至少对于库代码而言)的充分理由。当像phar这样的替代包装模型出现时,这些将您限制在严格的目录结构中。
更新:由于Wiki的Web存档版本非常丑陋,因此,我复制了以下最令人信服的原因:
- 必须使用include_path才能使用(PEAR)包。这使得很难将PEAR程序包与其自身的include_path捆绑到另一个应用程序中,创建包含所需类的单个文件,将PEAR程序包移至phar归档文件而又无需大量修改源代码的情况。
- 当顶级require_once与条件require_once混合使用时,这可能会导致操作码缓存(例如APC)无法将其缓存,该代码将与PHP 6捆绑在一起。
- 相对require_once要求include_path已经设置为正确的值,因此,如果没有正确的include_path,就不可能使用软件包
该*_once()
函数会统计每个父目录,以确保您要包含的文件与已包含的文件不同。这是经济放缓的部分原因。
我建议使用Siege之类的工具进行基准测试。您可以尝试所有建议的方法并比较响应时间。
有关更多信息,请require_once()
访问Tech Your Universe。
它与速度无关。这是关于优雅地失败。
如果require_once()失败,则脚本已完成。没有其他处理。如果使用include_once(),则脚本的其余部分将尝试继续呈现,因此您的用户可能对脚本失败的某些问题一无所知。