我正在做一些PHP内存基准测试,我想知道是否有一种方法可以优化垃圾收集器以减少内存消耗(在其他语言中,如JAVA)。
我在php.ini中只找到了三个与GC相关的可自定义参数:session.gc_probability
、session.gc_divisor
和session.gc_maxlifetime
。这只是针对会话,我并没有对此进行基准测试。
到目前为止,我所知道的可能的优化是与代码相关的,比如避免循环引用,以及通过调用gc_collect_cycles()
强制执行垃圾收集循环(感谢本文http://www.alexatnet.com/comment/86)。
有人知道PHP内存管理中的配置技巧或良好实践吗?
据我所知,使用gc_collect_cycles()
强制收集是最接近于在时间t最小化内存使用的方法。
GC当然是一个有时间限制的操作,应用程序很少有严格的内存限制,因此在整个程序执行过程中提供不必要地触发GC运行的选项是没有意义的。按照这种思路,PHP确实提供了按需打开和关闭循环收集器(gc_enable()
、gc_disable()
)的能力,这样您就可以根据时间优化代码(避免GC决定启动的开销)——当然,很容易看出这可能有多有用。
一般来说,PHP从哲学上避免了诸如内存管理和GC之类的主题,您的基准测试可能应该注意这一点,以获得更真实的前景。
希望这能有所帮助。
(对@GordonM引用微优化的评论表示赞同。)
通常,PHPs的内存管理对您声明和使用的所有变量都是开箱即用的。使用refcounting概念,PHP可以查看变量是否不再使用,然后自动清理它。
垃圾收集器只对对象具有循环引用的情况感兴趣,A指向B,B指向A。在这种情况下,引用计数不起作用。
如果PHP内存中正好有10.000个对象可能是循环的,可能不再使用,那么PHP垃圾收集器会在启用时触发,默认情况下是这样。您可以在运行时使用gc_enable()
和gc_disable()
禁用或启用它。
您也可以调用gc_collect_cycles()
手动清理这些对象。
但是,如果需要的话,如何优化这个过程呢?运行循环收集器并不一定要高效或有用,从10000个潜在对象来看,其中许多对象可能仍在使用中,无法清理。在这种情况下,检查所有对象并决定不清理它们是在浪费CPU周期。如果你这样做,就不会减少内存。
GC通常只在长时间运行的脚本中触发,有时只在创建过多对象的短时间web请求中触发。一般来说,你不应该想太多,因为默认情况适用于99%的用例。
使用"garbage_stats"PHP扩展,您可以访问有关GC运行效率和速度以及减少内存的度量和统计信息。它在PHP7+上工作(因为钩子从那时起才可用):https://github.com/tideways/php_garbage_stats
如果您已经安装了扩展,您可以通过调用以下命令来查看CLI脚本的垃圾收集统计信息:
$ php -dgc_stats.enable=1 -dgc_stats.show_report=1 test.php
Found 7 garbage collection runs in current script.
Collected | Efficency% | Duration | Memory Before | Memory After | Reduction% | Function
----------|------------|----------|---------------|--------------|------------|---------
0 | 0.00 % | 0.01 ms | 365824 | 366320 | -0.14 % | gc_collect_cycles
10000 | 100.00 % | 2.75 ms | 4651320 | 491816 | 89.43 % | foo
10000 | 100.00 % | 3.54 ms | 4652784 | 493280 | 89.40 % | foo
10000 | 100.00 % | 2.11 ms | 4654248 | 494744 | 89.37 % | foo
10000 | 100.00 % | 3.26 ms | 4656168 | 496664 | 89.33 % | Test::foo
9000 | 90.00 % | 1.51 ms | 4694680 | 951176 | 79.74 % | Test::foo
10000 | 100.00 % | 3.11 ms | 5112272 | 952768 | 81.36 % | Test::foo
从web(例如Apache或FPM)请求中,可以使用函数$runs = gc_stats();
访问这些信息并将其写入日志文件。
基于这些信息,您可以做出唯一可能优化的决定:根据GC的运行效率在脚本中启用或禁用GC。
当我不再需要变量时,我使用unset()方法来清除它。