在哪里可以找到触发 unset() 上的垃圾收集的“低内存”和“空闲 CPU 周期”调用?

Posted

技术标签:

【中文标题】在哪里可以找到触发 unset() 上的垃圾收集的“低内存”和“空闲 CPU 周期”调用?【英文标题】:Where to find the "low memory" and "free CPU cycles" calls triggering garbage collection on unset()? 【发布时间】:2013-12-12 09:30:00 【问题描述】:

当我解释 php unset() 不会立即触发“垃圾收集”时,我经常发现引用以下引用,但只有在它认为合适时才会触发(强调我的):

unset() 就像它的名字所说的那样 - 取消设置变量。它不会强制立即释放内存。 PHP的 垃圾收集器会在它发生时 看到合适 - 有意尽快,如 无论如何,那些 CPU 周期是不需要的, 或者在脚本之前 内存不足,无论发生什么 首先。

如果你正在做 $whatever = null; 那么你正在重写变量的 数据。你可能会释​​放内存/ 缩小得更快,但它可能会窃取 CPU 从真正需要的代码循环 他们更快,导致更长的时间 总执行时间。

我想知道用于触发垃圾收集的“低内存”和“空闲 CPU 周期”的 C 代码如何准确地工作,以及它在 PHP 5.2.x 和 PHP 5.3+ 之间是否存在差异。

所以我下载了 PHP 5.2.17 的 C 源文件并试图找到正确的代码部分。可能是我瞎了,也可能是我的C语言太低了,没找到这样的代码。

有人可以指点我正确的 C 文件吗?

编辑

在搜索上述引用的示例用法时,我意识到了一些奇怪的事情。

一些示例,例如https://***.com/a/584982/693207,通过使用以下 URL 对 php.net 发表评论来引用此引用:http://us2.php.net/manual/en/function.unset.php#86347。

浏览此 URL 仅显示 unset() 手册的顶部。缺少注释 #86347。

检查回程机器显示,这条评论 DID exist 来自 2008 年 10 月,但在 2012 年 9 月或之后的某个时间消失了(原因未知)。

也许那句话是,而且一直是,完全是错误的?

或者有没有人可以指出正确的 C 文件?

【问题讨论】:

我很想知道垃圾收集器实际上是如何决定工作的。真正的触发因素是什么......因为“当它认为合适时会这样做”不是不言自明的。 +1 我相信这就是你想看的地方:lxr.php.net/xref/PHP_5_2/main/alloca.c 你在哪里读到那句话?他们所描述的没有意义。 @duskwuff:这里例如:***.com/q/584960/693207 如果它写在用户注释中,你应该始终认为它是错误的,除非你有强有力的证据不相信;) 【参考方案1】:

好的,现在是 PHP 神话终结者的时候了!请先阅读有关垃圾收集如何工作的 PHP 文档,因为我将假设您对这一切如何工作有一些先验知识:

Reference Counting Basics Collecting Cycles

第二个文档具体解释了是什么触发了循环垃圾收集器的运行。它与“空闲 CPU 周期”或“低内存”无关——它完全基于存在的潜在垃圾对象的数量:

当垃圾收集器打开时,只要根缓冲区满了,就会执行上述循环查找算法。根缓冲区的大小固定为 10,000 个可能的根。

也就是说,循环垃圾收集器在累积了一定数量的潜在垃圾对象时运行,无论这些对象的大小如何。查看zend_gc.c 中的代码证实了这一点——其中肯定没有任何东西可以检查可用内存的总量,当然也不需要在CPU 空闲时让GC 运行所需的线程。所以我认为我们可以称这部分为“失败”。


接下来,让我们看看$x = nullunset($x) 之间的实际区别可能是什么。首先,让我们确认他们使用这个类与我们的 Buster the Test Dummy 做同样的事情:

class NoisyDestructor 
    function __destruct() 
        print "Destructor called\n";
    

现在,让我们看看将变量设置为 null 和 unset()-ing 它有什么区别:

$x = new NoisyDestructor();
print "Created\n";
$x = null;
print "Nulled\n";

print "\n";

$x = new NoisyDestructor();
print "Created\n";
unset($x);
print "Unset\n";

当我们运行它时,我们看到:

Created
Destructor called
Nulled

Created
Destructor called
Unset

等一下——这两个顺序完全相同!两者在功能上没有区别。现在,性能如何?

class Thing  

$start = microtime(true);
for ($i = 0; $i < 1e6; $i++) 
    $x = new Thing();
    $x = null;

printf("%f sec for null\n", microtime(true) - $start);

$start = microtime(true);
for ($i = 0; $i < 1e6; $i++) 
    $x = new Thing();
    unset($x);

printf("%f sec for unset\n", microtime(true) - $start);

现在,使用我的笔记本电脑进行测试,使用 PHP 5.4,我得到:

0.130396 sec for null
0.175086 sec for unset

考虑到我们必须运行这个循环多少次才能看到这个结果,将变量设置为 null 和取消设置它之间的性能差异不仅很小,而且实际上与 完全相反该评论声称:unset() 慢了大约 25%!这个 PHP 神话已经被彻底打破了。

TL;DR:您找到的引用完全错误。 (似乎正是出于这个原因,它已从 PHP.net 中删除。)

【讨论】:

您看到 null 和 unset 之间的时间差异的原因是您在全局范围内进行了测试。在功能范围内,差异会小很多。全局范围使用符号表,未设置的代码将不断地从中删除和添加桶(这很慢)。在本地范围内将没有符号表,所有内容都通过 CV 表处理,因此只需要销毁和创建 zval,这两种代码的发生方式大致相同。 @duskwuff:谢谢,很好的回答^^ @duskwuff 还有一个问题:zend_gc.c 是在 PHP 5.3 (iirc) 中引入的。 GC 在 PHP 5.2 中是如何工作的?和 PHP 5.3+ 有区别吗? PHP 5.2 中没有垃圾收集器,因此循环引用永远不会被收集。不过,仍然存在引用计数,因此大多数对象在不再被引用时仍会被收集。

以上是关于在哪里可以找到触发 unset() 上的垃圾收集的“低内存”和“空闲 CPU 周期”调用?的主要内容,如果未能解决你的问题,请参考以下文章

垃圾收集器与内存分配策略---垃圾收集算法

Java垃圾收集算法

G1 GC垃圾收集流程

在 AS3 中强制垃圾收集?

垃圾收集导致连接的套接字延迟(NodeJS 服务器)

如何在 PHP 中对这个线性链表使用 unset()