为什么require_once不好用?


143

我所读到的有关更好的PHP编码实践的所有内容一直在说不要require_once因为速度而使用。

为什么是这样?

做相同的事情的正确/更好的方法是什么require_once?如果有关系,我正在使用PHP 5。


9
这个问题现在已经很老了,答案也不再是可疑的了。很
高兴

Answers:


108

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_oncehhvm。同样,这仅在您运行*_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.

29
我怀疑您的define()方法是否比内置查找表快,但是我同意您的总体观点-肯定不是问题吗?
鲍比·杰克

1
我相当确定您是对的鲍比(Bobby),但我不提倡_once的定义。这只是一个选择。解释代码所花费的时间甚至可能使它稍微慢一些,但是,也就是说,我不知道内部方法有多彻底。它可能会做额外的工作以确保没有重复。
奥利

8
另一个缺点是APC无法缓存IIRC的include_once和require_once调用
dcousineau

3
我只是对这两种方法进行了非常基本的测试-我进行了1,000,000次迭代,其中包括一个简单地将常量“ testinclude”定义为true的文件。在第一个测试中,我使用了require_once,第二个测试中使用了if(!defined('testinclude')),结果很有趣:需求:0.81639003753662未定义:0.17906713485718定义的速度提高了0.63732290267944微秒。
特拉维斯·韦斯顿2014年

使用define的情况会更快,因为您不执行任何其他类型的检查,例如使用realpath。它不是在比较两个完全相同的事物。
jgmjgm

150

这个线程让我感到畏缩,因为已经有一个“解决方案发布”,而且就所有意图和目的而言都是错误的。让我们列举一下:

  1. 在PHP中,定义确实非常昂贵。您可以自己查找或测试它,但是在PHP中定义全局常量的唯一有效方法是通过扩展。(类常量实际上是相当不错的性能,但这是有争议的,因为2)

  2. 如果使用require_once()得当,即包含类,则甚至不需要定义。只要检查是否class_exists('Classname')。如果您要包含的文件包含代码,即您以程序方式使用它,则绝对没有理由require_once()对您有必要。每次包含文件时,您都假定要进行子例程调用。

所以有一段时间,很多人的确使用了该class_exists()方法。我不喜欢它,因为它很丑陋,但是他们有充分的理由:require_once()在一些最新版本的PHP之前效率很低。但这是固定的,我的争辩是,为条件而必须编译的额外字节码和额外的方法调用将远远超过任何内部哈希表检查。

现在,承认:这些东西很难测试,因为它只占执行时间的很少。

这是您应该考虑的问题:通常,在PHP中包含昂贵的代码,因为每次解释器命中时,它都必须切换回解析模式,生成操作码,然后再跳回。如果您有100个以上的包含项,则肯定会对性能产生影响。使用或不使用require_once如此重要的问题的原因是,因为这使操作码缓存变得困难。可以在这里找到对此解释,但是归结为:

  • 如果在解析期间,您确切知道请求整个生命周期中需要哪些包含文件,require()那么一开始它们和操作码缓存将为您处理其他所有文件。

  • 如果您没有运行操作码缓存,那么您将处于困境。将所有包含内容内联到一个文件中(在开发过程中请勿执行此操作,仅在生产环境中执行此操作)当然可以帮助解析时间,但这是一件很麻烦的事,而且,您还需要确切地知道在请求。

  • 自动加载非常方便,但是很慢,原因是每次完成包含操作时都必须运行自动加载逻辑。在实践中,我发现针对一个请求自动加载多个专用文件不会造成太大的问题,但是您不应该自动加载所需的所有文件。

  • 如果有可能10包括(这是一个非常回信封计算),这一切手淫是不值得的:只是优化您的数据库查询或东西。


14
这已经有4年的历史了,并且在大多数情况下不再适用define()require_once()并且defined()在我的计算机上每个都需要1-2微秒。
Daniel Beardsley

72
但这是用户获得页面的2毫秒。一年的网页浏览量可以为用户节省3秒钟!那时他们可以看十分之一广告!想想用户。不要浪费微秒。
安德鲁·恩斯利

15
如此一来,每个人都知道了讽刺,微秒是1/1000000秒。
安迪·蔡斯

1
@AndrewEnsley您完全被自己的讽刺弄错了。您对PHP也可以在微处理器上运行这一事实一无所知,在您的PC上1毫秒代表在微处理器上几毫秒。现在,拥有20个包含文件和一个更大的项目该怎么办?这是引入的几毫秒延迟的20倍,所以我们已经达到了人类可以注意到的点。如果频繁调用该脚本,将导致系统性能问题。优化不是开玩笑,整个世界也不会扭转您的PC的局面。有上万个CPU正在使用。
约翰·

2
@约翰。这是开怀大笑的笑话。没有恶意。如果您值得优化收录内容,请继续。
安德鲁·恩斯利

66

我很好奇,并查看了亚当·贝克斯特伦(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个req​​uire_once调用平均花费10.15秒才能完成,而10,000个req​​uire调用平均花费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文档)。


6
而且,您必须运行10,000次才能看到如此微小的差异,任何“优化”都不值得担心。使用探查器,找出真正的瓶颈在您的应用程序中。我怀疑这个问题是瓶颈。
DGM

1
这一切的意思是,这根本不重要。从逻辑上使用对您更有效的方法。
Buttle Butkus 2012年

瓦特

21

您能给我们提供任何指向这些编码实践的链接来避免这种情况吗?就我而言,这是一个完全没有问题的问题。我自己没有看过源代码,但是我想include和之间的唯一区别include_onceinclude_once将文件名添加到数组中并每次检查数组。使该数组排序很容易,因此对其进行搜索应为O(log n),即使是中等规模的应用程序也只能包含几十个包含项。


一个是,chazzuka.com / blog /?p = 163他们确实没有“不去”,但是有太多“昂贵”的东西加起来。实际上,所有包含/所需的文件都添加到内部数组中(通过函数返回它),我想_once必须循环该数组并执行strcmp,这将加起来
Uberfuzzy

7

做事的更好方法是使用面向对象的方法并使用__autoload()


3
但您链接到的自动加载对象页面中的第一个示例使用require_once
Shabbyrobe'Oct

我不买这个。在很多情况下,OO不能像其他范例那样适当地适用,因此您不应强迫OO仅仅通过__autoload()获得任何微小的好处。
鲍比·杰克

1
您会认为自动加载实际上需要的时间比* _once更长(假设您只需要您需要的内容)。
尼克

不,不是,至少不是绝对,仍然需要以某种方式包括自动加载功能,这是PHP在错误失败之前的不得已的手段-因此,实际上,PHP实际上实际上在所有地方都进行了不必要的不​​必要的检查,这些检查将适用于include / require和AFTER THAT它会调用自动加载(如有规定)... PS:__autoload()不鼓励,并可能在未来被废弃,你应该使用spl_autoload_register(...)这些天...... PS2:不要误会我的意思,我使用的自动加载功能有时; )
jave.web '16

6

它没有使用不好的功能。在整体代码库中,对如何以及何时使用它的理解是错误的。我将为可能被误解的概念添加更多上下文:

人们不应该认为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_once15次,或获得良好的视觉效果:

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函数在必要时很有用,但不应视为在各处用于加载所有类的整体解决方案。


5

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,就不可能使用软件包

5

*_once()函数会统计每个父目录,以确保您要包含的文件与已包含的文件不同。这是经济放缓的部分原因。

我建议使用Siege之类的工具进行基准测试。您可以尝试所有建议的方法并比较响应时间。

有关更多信息,请require_once()访问Tech Your Universe


感谢您指向本文的指针。require_once()是包含双重文件的很好的安全带,我们将继续使用它,但是能够使其变得干净是很好的。
安迪·莱斯特

2

即使require_once和比和(或可能存在其他选择)include_once 慢,我们在这里讨论的是最小级别的微优化。与担心类似之类的事情相比,花时间优化写得不好的循环或数据库查询要好得多。requireincluderequire_once

现在,有人可能会说这require_once允许使用较差的编码做法,因为您不需要注意保持包含的内容整洁有序,但这与功能本身(尤其是其速度)无关。

显然,出于代码清洁和易于维护的考虑,自动加载会更好,但是我想弄清楚这与speed无关。


0

您可以使用include,oli的替代品和__autoload()进行测试;并使用安装的APC之类的软件对其进行测试。

我怀疑使用常量会加快速度。


0

是的,它比普通的require()稍贵。我认为,要点是,如果您可以使代码保持足够的组织性,以免重复包含,请不要使用* _once()函数,因为这样可以节省一些周期。

但是使用_once()函数不会杀死您的应用程序。基本上,只是不用它作为借口就不必组织include了。在某些情况下,仍然不可避免地要使用它,这没什么大不了的。


-2

我认为在PEAR文档中,对require,require_once,include和include_once有建议。我确实遵循该准则。您的应用程序将更加清晰。


一些参考将是有序的。
彼得·莫滕森

-3

它与速度无关。这是关于优雅地失败。

如果require_once()失败,则脚本已完成。没有其他处理。如果使用include_once(),则脚本的其余部分将尝试继续呈现,因此您的用户可能对脚本失败的某些问题一无所知。


1
不必要。实际上,您可以挂钩错误处理程序或关闭处理程序,以为用户提供一个不错的错误页面(尽管人们很少这样做)。作为开发人员,我宁愿事情立即出错。
爱德华·Z·杨

1
或者,视情况而定,不能正常失败-如果某些重要文件未正确执行require(),则最好放弃并停止。但这是require vs include,而我认为问题更多地集中在require vs require_once。
HoboBen

-4

我个人认为使用require_once(或include_once)是一种不好的做法,因为require_once会检查您是否已包含该文件,并抑制重复包含的文件的错误,从而导致致命错误(如重复声明函数/类/等)。 。

您应该知道是否需要包含文件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.