msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

12 Sep 12 再一次, 不要使用(include/require)_once

最近关于apc.include_once_override的去留, 我们做了几次讨论, 这个APC的配置项一直一来就没有被很好的实现过.

在这里, 我想和大家在此分享下, 这个问题的原因, 以及对我们的一些启示.

关于使用include还是include_once(以下,都包含require_once), 这个讨论很长了, 结论也一直有, 就是尽量使用include, 而不是include_once, 以前最多的理由的是, include_once需要查询一遍已加载的文件列表, 确认是否存在, 然后再加载.

诚然, 这个理由是对的, 不过, 我今天要说的, 是另外一个的原因.

我们知道, PHP去判断一个文件是否被加载, 是需要得到这个文件的opened_path的, 意思是说, 比如:

<?php
set_include_path("/tmp/:/tmp2/");
include_once("2.php");
?>

当PHP看到include_once “2.php”的时候, 他并不知道这个文件的实际路径是什么, 也就无法从已加载的文件列表去判断是否已经加载, 所以在include_once的实现中, 会首先尝试解析这个文件的真实路径(对于普通文件这个解析仅仅类似是检查getcwd和文件路径, 所以如果是相对路径, 一般是不会成功), 如果解析成功, 则查找EG(include_files), 如果存在则说明包含过了, 返回, 否则open这个文件, 从而得到这个文件的opened_path. 比如上面的例子, 这个文件存在于 “/tmp2/2.php”.

然后, 得到了这个opened_path以后, PHP去已加载的文件列表去查找, 是否已经包含, 如果没有包含, 那么就直接compile, 不再需要open file了.

1. 尝试解析文件的绝对路径, 如果能解析成功, 则检查EG(included_files), 存在则返回, 不存在继续
2. 打开文件, 得到文件的打开路径(opened path)
3. 拿opened path去EG(included_files)查找, 是否存在, 如果存在则返回, 不存在继续
4. 编译文件(compile_file)

这个在大多数情况下, 不是问题, 然而问题出在当你使用APC的时候…

在使用APC的时候, APC劫持了compile_file这个编译文件的指针, 从而直接从cache中得到编译结果, 避免了对实际文件的open, 避免了对open的system call.

然而, 当你在代码中使用include_once的时候, 在compile_file之前, PHP已经尝试去open file了, 然后才进入被APC劫持的compile file中, 这样一来, 就会产生一次额外的open操作. 而APC正是为了解决这个问题, 引入了include_once_override, 在include_once_override开启的情况下, APC会劫持PHP的ZEND_INCLUDE_OR_EVAL opcode handler, 通过stat来确定文件的绝对路径, 然后如果发现没有被加载, 就改写opcode为include, 做一个tricky解决方案.

但是, 很可惜, 如我所说, APC的include_once_override实现的一直不好, 会有一些未定义的问题, 比如:

<?php
set_include_path("/tmp");
function a($arg = array()) {
    include_once("b.php");
}

a();
a();
?>

然后, 我们的b.php放置在”/tmp/b.php”, 内容如下:

<?php
  class B {}
?>

那么在打开apc.include_once_override的情况下, 连续访问就会得到如下错误:

Fatal error - include() : Cannot redeclare class b

(后记 2012-09-15 02:07:20: 这个APC的bug我已经修复: #63070)

排除这些技术因素, 我也一直认为, 我们应该使用include, 而不是include_once, 因为我们完全能做到自己规划, 一个文件只被加载一次. 还可以借助自动加载, 来做到这一点.

你使用include_once, 只能证明, 你对自己的代码没信心.

所以, 建议大家, 不要再使用include_once


分享到:



Related Posts:

Tags: , ,

76 Responses to “再一次, 不要使用(include/require)_once”

Pages: [2] 1 » Show All

  1. 谈腾 |

    时过境迁,现在都用 composer 自动加载

  2. Gordon |

    請問, 對於現在的opcache, 是否還不建議使用include/require once?

    謝謝.

  3. Cheap Lebron XI Shoes |

    Construction of the trunk is very good.
    Cheap Lebron XI Shoes http://www.predaptive.net/m568/

  4. Anonymous |

    随着项目的复杂化,开发者进入了一个不断埋坑又填坑的循环。结果是能力没有提高,却花费大量的时间在查找上。看看现在网上的一些所谓开源PHP商城的代码,那个渣的程度已经不能直视了。

  5. cola |

    APC可以在PHP5.5下跑吗?

  6. RichardXu |

    接上贴,补充几点:
    1、如果能保证文件只加载一次,那绝对选择 require/inlcude;

    2、如果无法保证文件只被加载一次,优先选择 (require/include)_once,因为定义语句也是要执行的,会给变量分配内存,构造数组等。

    鸟哥强调的是第一点

  7. RichardXu |

    首先,总体来说,require_once 肯定要比 require 性能好。

    因为 require 某个文件等同于 “编译 + 执行” 这个文件;require_once 避免了对相同文件的重复 “编译” 和 “执行”。
    即使使用APC大大降低了“编译”阶段的消耗,但是“执行”阶段依然每次都会做重复工作。

    其次,鸟哥说的应该是在PHP5.2之前,require_once 的实现机制不健全,只有当参数传的是绝对路径,才会根据路径去确认该文件是否加载过,否则就会 open 这个文件,这显然很不合理。
    PHP5.3之后,开始支持相对路径;

    但即使是这种场景,也不是 require 绝对比 require_once 性能好,而只是对那些定义比较多的PHP文件是这样;执行代码比较多的场景就一定不是这样了。

    综上所述,require_once 从设计思路上来说是要比 require 性能高的;
    具体来说
    PHP5.2之前:
    1、如果使用的是绝对路径,使用 require_once;
    2、如果是相对路径的定义文件,使用了APC,选择 require;没有使用 APC,使用 require_once;
    3、如果是相对路径的偏执行文件,使用 require_once
    PHP5.3之后:
    1、对于定义性的文件,如果用了APC,使用 require_once 性能稍高一些;没有APC,当然是 require_once ;
    2、对于有执行代码的文件,当然是 require_once;

  8. 没落皇族 |

    差不多就得了,我们的生命可以支持我们做多久的程序开发?无聊不?有限的时间里多陪陪家人吧,至于代码,能跑起来就OK了,纠结一些瓜皮之事,得不偿失!

  9. Muhammad |

    What’s up, this weekend is pleasant in support
    of me, as this time i am reading this great informative article here at my
    house.

    homepage, Orlando,

  10. php中的多余的require(),这样会导致多余的执行时间吗? | segment-解决方案 |

    [...] ,被PHP开发组的laruence建议避免使用 ————————————————————————— [...]

  11. consatan |

    1. 尝试解析文件的绝对路径, 如果能解析成功, 则检查EG(included_files), 存在则返回, 不存在继续

    存在则返回的话,那为什么在function中调用include_once(‘/path/to/file.php’)后会导致外部引入同一个文件时失败呢?

    function a() {
    include_once(‘/path/to/file.php’);
    var_dump($var);
    }
    a(); // output success
    include_once(‘/path/to/file.php’);
    var_dump($var); // output NULL

    “存在则返回”仅仅是标记了下“引入的文件已经存在”然后就不引入了??

  12. Verses |

    樓下說的用命名來載入什麼意思? 會比 include好嗎?

  13. How To Set Up A Website |

    Great article.

  14. 小谈博客 |

    有了命名空间,是不是不用考虑include ,require了,自动导入啊

  15. soooldier |

    我对鸟哥关于include_once调用过程描述中的第3步不理解:为什么还需要去included_files里查找一次?

  16. soooldier |

    还有鸟哥对于include_once过程描述中的第3步不太理解:为什么还需要再去included_files查找一次?

  17. soooldier |

    我的理解与之相反,如果把重复文件包含的工作交给程序员去做的话避免不了会建一个类似“已加载”的类的集合。单拿判断“已加载”这段代码来说,我觉得由底层做更合适,毕竟程序员自己写一个的话那么最终是要通过各种解析成opcode,这样的效率应该会比底层的C实现低下。而至于APC对include_once(require_once)造成的影响那就另说了,这个属于APC的实现问题,而不应该把问题归结到对include_once(require_once)的使用上。

  18. 上海天气预报 |

    一再强调,队友死型不改,对于有洁癖的程序员来说有时真心累 。。

  19. wsq |

    也许可以给require_once加上一个参数,限制open file的频率。

  20. xsir317 |

    怎么感觉APC用了好多HACK的手段。。。

  21. roger |

    其实有时候并不是对自己的代码没有信心,而是在接手别人的代码的时候,为了安全起见,不得已而为之

  22. Ill tell you some sneak preview along with my favorite comfortable shoes origina |

    Khmer Comedy » Somnangblogs I was proposed this internet site by my own cousin. I am not sure whether this post is published by him since nobody in addition know this sort of detailed with regards to my trouble. You’re incredible! Thanks! your write-up about Khmer Humourous » Somnangblogs All the best Lisa Veronica

  23. 关于Yaf的一些说明 | 午后小憩 |

    [...] 再一次, 不要使用(include/require)_once [...]

  24. 再一次, 不要使用(include/require)_once | 午后小憩 |

    [...] 本文地址: http://www.laruence.com/2012/09/12/2765.html [...]

  25. kknd li |

    之所以有once的主要原因是PHP在早期的开发实践中对开发者的约束很少项目又相对简单。
    当引入了面向对象后虽然有了自动加载的机制但还是依耐开发人员的的实现而且对早期函数式编程并没有改进和支持。对很多开发者来说这不能不说是个负担,实际上现在的PHP已经没有刚开始时的便捷轻便的特点开始变得复杂而有点脆弱。随着项目的复杂化对开发者的要求就进入一个陡峭提高的过程,当项目在你眼里慢慢走向靠拼凑来维持的时候,当你周围的人靠_once来保证不重复加载你又无能为力的时候不得不说对这种语言充满的是怎么样的感觉。

  26. 深入研究$ » 再一次, 不要使用(include/require)_once |

    [...] 本文地址: http://www.laruence.com/2012/09/12/2765.html [...]

Pages: [2] 1 » Show All

Leave a Reply

*