当前位置:  开发笔记 > 编程语言 > 正文

为什么require_once如此糟糕?

如何解决《为什么require_once如此糟糕?》经验,为你挑选了8个好方法。

我读到的关于更好的PHP编码实践的一切都在说不要require_once因为速度而使用.

为什么是这样?

做同样事情的正确/更好的方法是什么require_once?如果重要,我使用的是PHP5.



1> Edward Z. Ya..:

这个帖子让我感到畏缩,因为已经出现了"解决方案",而且出于所有意图和目的,这是错误的.我们列举一下:

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

    如果你require_once()正确使用,也就是说,为了包含类,你甚至不需要定义; 只是检查一下class_exists('Classname').如果您所包含的文件包含代码,即您以程序方式使用它,则绝对没有require_once()必要为您提供理由; 每次你包含你假定要进行子程序调用的文件.

所以有一段时间,很多人确实使用这种class_exists()方法进行包含.我不喜欢它,因为它很难,但他们有充分的理由:require_once()在一些最新版本的PHP之前效率很低.但是这已得到修复,我认为你必须为条件和额外方法调用编译的额外字节码远远超过任何内部哈希表检查.

现在,承认:这个东西很难测试,因为它占据了很少的执行时间.

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

如果在解析期间,您确切知道请求的整个生命周期中将包含哪些文件require(),那么最开始的那些文件以及操作码缓存将为您处理其他所有内容.

如果您没有运行操作码缓存,那么您将处于困境.将所有包含内容整合到一个文件中(不要在开发期间执行此操作,仅在生产中)可以帮助解析时间,但这样做很麻烦,而且,您需要确切地知道您将在请求.

自动加载非常方便,但速度很慢,因为每次完成包含时都必须运行自动加载逻辑.在实践中,我发现为一个请求自动加载几个专用文件不会造成太多问题,但是你不应该自动加载你需要的所有文件.

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


但是,用户拥有该页面的时间会快2微秒.超过一年的页面浏览量,这可以节省用户整整3秒!他们当时可以观看十分之一的商业广告!想想用户.不要浪费微秒.
只是这样每个人都知道这个讽刺,微秒是1/1000000秒.
这是4年之久,大部分时间不再适用,`define()`,`require_once()`和`defined()`在我的机器上每个大约需要1-2微秒.
@约翰。这是开怀大笑的笑话。没有恶意。如果您值得优化收录内容,请继续。

2> Oli..:

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数千次,这只与您的代码相关.






我怀疑你的defined()方法比内置的查找表更快,但我同意你的整体观点 - 肯定不是问题?!
另一个缺点是APC没有缓存include_once和require_once调用IIRC
我只是对这两种方法做了一个非常基本的测试 - 我做了1,000,000次迭代,包括一个简单地将常量'testinclude'定义为true的文件.在第一个测试中,我使用了require_once,第二个我使用if(!defined('testinclude'))并且结果很有趣:要求:0.81639003753662未定义:0.17906713485718定义为0.63732290267944微秒更快.

3> terson..:

我很好奇并检查了Adam Backstrom与Tech Your Universe的链接.本文介绍了应该使用require而不是require_once的原因之一.但是,他们的说法并不符合我的分析.我有兴趣看到我可能错误地分析了解决方案.我使用php 5.2.0进行比较.

我开始创建100个头文件,使用require_once包含另一个头文件.每个文件看起来像:


我使用快速bash hack创建了这些:

for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
    echo " $i
    cat helper.php >> $i;
done

通过这种方式,我可以轻松地在使用require_once和包含头文件时的require之间进行切换.然后我创建了一个app.php来加载一百个文件.这看起来像:


我将require_once标头与使用头文件的require标头进行了对比,如下所示:


使用require与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个需要调用平均使用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文件中引用的相对路径.如果我将它作为绝对引用,那么唯一的区别是代码中的附加时间(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

这似乎意味着您可以通过使用绝对路径而不是相对路径来减少系统调用的数量.除此之外的唯一区别是时间(NULL)调用,它们似乎用于检测代码以比较更快的代码.

另一个注意事项是APC优化包有一个名为"apc.include_once_override"的选项,它声称它减少了require_once和include_once调用所进行的系统调用次数(参见PHP文档).

对不起,很长的帖子.我很好奇.


任何"优化",你必须运行10,000次才能看到如此微小的差异甚至不值得担心.使用分析器并找出应用程序中*real*瓶颈的位置.我怀疑这个问题是瓶颈.

4> nickf..:

您能否告诉我们这些编码实践的任何链接,以避免它?就我而言,这是一个完整的非问题.我自己没有看过源代码,但我想,include和之间的唯一区别include_onceinclude_once将文件名添加到数组并每次检查数组.保持该数组排序很容易,因此搜索它应该是O(log n),即使是中等大的应用程序也只有几十个包含.



5> Greg..:

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


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

6> Annika Backs..:

*_once()每个父目录函数stat以确保您包含的文件与已包含的文件不同.这是放缓原因的一部分.

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

更多关于require_once()在技术Universe的.



7> Steve Clay..:

PEAR2维基(当它存在时)用于列出放弃所有 require/include指令的好理由,支持自动加载,至少对于库代码而言.当像phar这样的替代包装模型即将出现时,这些将您绑定到严格的目录结构.

更新:由于维基的网络存档版本令人眼花缭乱,我复制了下面最令人信服的理由:

include_path是使用(PEAR)包所必需的.这使得很难将PEAR包与其自己的include_path捆绑在另一个应用程序中,以创建包含所需类的单个文件,以便将PEAR包移动到phar存档而无需进行大量源代码修改.

当顶级require_once与条件require_once混合时,这可能导致代码无法通过操作码缓存(如APC)进行无法缓存,这些代码将与PHP 6捆绑在一起.

相对require_once要求include_path已经设置为正确的值,这样就无法使用没有正确include_path的包



8> hexalys..:

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

人们不应该认为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该类旨在使用所有其他3个类.很公平!但是现在如果访问者正在浏览网站甚至没有登录并且框架加载:require_once("includes/user.php");对于每个请求都是如此.

它包括在该特定请求期间不会使用的1 + 3个不必要的类.这是一个膨胀的框架最终使用每个请求40MB而不是5MB或更少.


其他可能被误用的方式是当一个类被其他许多人重用时!假设您有大约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在任何给定请求中不使用该文件的概率为none,那么require肯定应该在您的主类中.分别require_once在每个班级中成为浪费资源.


require_once函数在必要时很有用,但它不应被视为在任何地方用于加载所有类的单片解决方案.

推荐阅读
吻过彩虹的脸_378
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有