mirror of
https://github.com/php/doc-zh.git
synced 2026-03-23 22:52:08 +01:00
* Fix features/: translations, security, missing content - remote-files.xml: remove duplicated 如果 - sessions.xml: 新引力→吸引力 - persistent-connections.xml: fix 自以为→用作 translation error - gc.xml: remove translator comment left in published text - http-auth.xml: add htmlspecialchars() for XSS protection, sync with EN - file-upload.xml: translate 2 untranslated English paragraphs, add missing "Uploading an entire directory" section * Update file-upload.xml 根据 https://github.com/php/doc-zh?tab=readme-ov-file#有关在翻译后的中文文件中的空格与换行的说明 更新内容换行 --------- Co-authored-by: mowangjuanzi <mowangjuanzi@petalmail.com>
520 lines
23 KiB
XML
520 lines
23 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
||
<!-- $Revision$ -->
|
||
<!-- EN-Revision: 23e84882d6654f995166d17e5610af6bf00ef18c Maintainer: mowangjuanzi Status: ready -->
|
||
<!-- CREDITS: Luffy -->
|
||
<chapter xml:id="features.gc" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<title>垃圾回收</title>
|
||
|
||
<para>
|
||
本节介绍了新垃圾回收(也称为 GC)机制作为 PHP 5.3 一部分的优点。
|
||
</para>
|
||
|
||
<sect1 xml:id="features.gc.refcounting-basics">
|
||
<title>引用计数基础</title>
|
||
<para>
|
||
PHP 变量存储在称为“zval”的容器中。zval 容器除了变量的类型和值之外,还包含两个额外的信息位。第一个是“is_ref”,是布尔值,表示变量是否是“引用集合”的一部分。通过这个位,PHP
|
||
引擎知道如何区分普通变量和引用。由于 PHP 允许用户自定义引用,通过 & 运算符创建引用,zval 容器还有内部引用计数机制来优化内存使用。第二个是“refcount”,表示有多少个变量名(也称为符号)指向这个
|
||
zval 容器。所有符号都存储在一个符号表中,每个作用域都有一个符号表。主脚本(即通过浏览器请求的脚本)有一个作用域,每个函数或方法也有一个作用域。
|
||
</para>
|
||
<para>
|
||
当使用常量值创建新变量时,也会创建 zval 容器,例如
|
||
<example>
|
||
<title>创建新 zval 容器</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$a = "new string";
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
在这种情况下,新的符号名称 <literal>a</literal> 会在当前作用域中创建,并且会创建新的变量容器,其类型为 <type>string</type>,值为
|
||
<literal>new string</literal>。由于没有创建用户定义的引用,“is_ref”位默认设置为 &false;。“refcount”设置为
|
||
<literal>1</literal>,因为只有一个符号使用了这个变量容器。请注意,具有“refcount”为 <literal>1</literal> 的引用(即"is_ref"为
|
||
&true;)会视为非引用(即“is_ref”为 &false;)。如果安装了 <link
|
||
xlink:href="&url.xdebug;">Xdebug</link>,可以通过调用 <function>xdebug_debug_zval</function> 来显示此信息。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>显示 zval 信息</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$a = "new string";
|
||
xdebug_debug_zval('a');
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=1, is_ref=0)='new string'
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
将这个变量赋值给另一变量名将增加 refcount 的计数。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>增加 zval 的 refcount</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$a = "new string";
|
||
$b = $a;
|
||
xdebug_debug_zval( 'a' );
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=2, is_ref=0)='new string'
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
这里的 refcount 是 <literal>2</literal>,因为同一个变量容器链接到 <varname>a</varname> 和 <varname>b</varname>。PHP
|
||
很聪明,当没有必要的时候,不会复制实际的变量容器。当“refcount”到 0 时,就会销毁变量容器。当链接到变量容器的任何符号离开作用域(例如函数结束时)或取消符号赋值(例如通过调用
|
||
<function>unset</function>)时,“refcount”会减少 1。以下是示例:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>减少 zval refcount</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$a = "new string";
|
||
$c = $b = $a;
|
||
xdebug_debug_zval( 'a' );
|
||
$b = 42;
|
||
xdebug_debug_zval( 'a' );
|
||
unset( $c );
|
||
xdebug_debug_zval( 'a' );
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=3, is_ref=0)='new string'
|
||
a: (refcount=2, is_ref=0)='new string'
|
||
a: (refcount=1, is_ref=0)='new string'
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
如果现在调用 <literal>unset($a);</literal>,变量容器,包含类型和值,会从内存中移除。
|
||
</para>
|
||
|
||
<sect2 xml:id="features.gc.compound-types">
|
||
<title>复合类型</title>
|
||
|
||
<para>
|
||
对于 <type>array</type> 和 <type>object</type> 这样的复合类型,情况会稍微复杂一些。与 <type>scalar</type>
|
||
值不同,<type>array</type> 和 <type>object</type> 的属性存储在自己的符号表中。这意味着以下示例将创建三个 zval 容器:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>创建 <type>array</type> zval</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$a = array( 'meaning' => 'life', 'number' => 42 );
|
||
xdebug_debug_zval( 'a' );
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=1, is_ref=0)=array (
|
||
'meaning' => (refcount=1, is_ref=0)='life',
|
||
'number' => (refcount=1, is_ref=0)=42
|
||
)
|
||
]]>
|
||
</screen>
|
||
<para>图示:</para>
|
||
<mediaobject>
|
||
<alt>简单数组的 zval</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/simple-array.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
这三个 zval 变量容器是 <varname>a</varname>、<varname>meaning</varname> 和
|
||
<varname>number</varname>。增加和减少“refcounts”的规则也适用于此。下面,再向数组添加一个元素,并将其值设置为已存在元素的内容:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>添加已存在的元素到数组</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$a = array( 'meaning' => 'life', 'number' => 42 );
|
||
$a['life'] = $a['meaning'];
|
||
xdebug_debug_zval( 'a' );
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=1, is_ref=0)=array (
|
||
'meaning' => (refcount=2, is_ref=0)='life',
|
||
'number' => (refcount=1, is_ref=0)=42,
|
||
'life' => (refcount=2, is_ref=0)='life'
|
||
)
|
||
]]>
|
||
</screen>
|
||
<para>图示:</para>
|
||
<mediaobject>
|
||
<alt>带有引用的简单数组的 zvals</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/simple-array2.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
从上面的 Xdebug 输出中,可以看到新旧的数组元素现在都指向“refcount”为 <literal>2</literal> 的 zval 容器。尽管 Xdebug
|
||
的输出显示了两个值为 <literal>'life'</literal> 的 zval 容器,但它们实际上是同一个。<function>xdebug_debug_zval</function>
|
||
函数没有显示这一点,但可以通过显示内存指针来看到它。
|
||
</para>
|
||
<para>
|
||
从数组中删除元素就像从作用域中删除符号一样。删除后,数组元素指向的容器的“refcount”会减少。同样,当“refcount”到 0 时,变量容器就会从内存中删除。再举个例子来说明这一点:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>从数组中删除元素</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$a = array( 'meaning' => 'life', 'number' => 42 );
|
||
$a['life'] = $a['meaning'];
|
||
unset( $a['meaning'], $a['number'] );
|
||
xdebug_debug_zval( 'a' );
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=1, is_ref=0)=array (
|
||
'life' => (refcount=1, is_ref=0)='life'
|
||
)
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
现在,如果将数组本身作为数组的一个元素添加进去,情况就会变得有趣起来。在下一个例子中这样做,并且偷偷加入引用运算符,否则 PHP 会创建副本:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>将数组添加为自身的一个元素</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
$a = array( 'one' );
|
||
$a[] =& $a;
|
||
xdebug_debug_zval( 'a' );
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=2, is_ref=1)=array (
|
||
0 => (refcount=1, is_ref=0)='one',
|
||
1 => (refcount=2, is_ref=1)=...
|
||
)
|
||
]]>
|
||
</screen>
|
||
<para>图示:</para>
|
||
<mediaobject>
|
||
<alt>具有循环引用的数组 zval</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/loop-array.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
可以看到数组变量(<varname>a</varname>)以及第二个元素(<varname>1</varname>)现在都指向“refcount”为 <literal>2</literal>
|
||
的变量容器。上面显示的“...”表示存在递归,这在这种情况下意味着“...”指向原数组。
|
||
</para>
|
||
<para>
|
||
就像之前一样,清除变量会删除符号,并且指向的变量容器的引用计数会减少 1。因此,如果在运行上述代码后清除变量 <varname>$a</varname>,那么
|
||
<varname>$a</varname> 和元素“1”所指向的变量容器的引用计数会减少 1,从“2”变为“1”。可以表示为:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>清除 <varname>$a</varname></title>
|
||
<screen>
|
||
<![CDATA[
|
||
(refcount=1, is_ref=1)=array (
|
||
0 => (refcount=1, is_ref=0)='one',
|
||
1 => (refcount=1, is_ref=1)=...
|
||
)
|
||
]]>
|
||
</screen>
|
||
<para>图示:</para>
|
||
<mediaobject>
|
||
<alt>在演示完内存泄漏的循环引用数组移除后的 zval</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/leak-array.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="features.gc.cleanup-problems">
|
||
<title>清理问题</title>
|
||
<para>
|
||
虽然在任何作用域中都没有指向这个结构的符号,却无法清理它,因为数组元素“1”仍然指向同一个数组。由于没有外部符号指向它,用户无法清理该结构;因此会出现内存泄漏。幸运的是,PHP
|
||
会在请求结束时清理这个数据结构,但在此之前,它会占用宝贵的内存空间。如果你正在实现解析算法或其他需要子级元素指向"父级"元素的情况,会经常发生。当然,object
|
||
也可能出现相同的情况,因为 object 始终隐式“<link linkend="language.oop5.references">引用</link>”。
|
||
</para>
|
||
<para>
|
||
如果这种情况只发生一两次,可能不是问题,但如果出现数千次,甚至数百万次的内存损失,显然就成了问题。这在长时间运行的脚本中尤为棘手,比如守护进程,其中请求基本上永远不会结束,或者在大量的单元测试集中。后者在运行
|
||
eZ Components 库的模板组件的单元测试时出现了问题。在某些情况下,它需要超过 2GB 的内存,而测试服务器并没有那么多内存可用。
|
||
</para>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="features.gc.collecting-cycles">
|
||
<title>回收循环</title>
|
||
<para>
|
||
传统上,像 PHP 之前使用的引用计数内存机制无法解决循环引用内存泄漏的问题;然而,从 5.3.0 版本开始,PHP 实施了<link
|
||
xlink:href="&url.gc-paper;">引用计数系统中的同步循环回收</link>论文中的同步算法来解决这个问题。
|
||
</para>
|
||
<para>
|
||
对算法的完全说明有点超出这部分内容的范围,将只介绍其中基础部分。首先,需要确立一些基本规则。如果 refcount 增加,则该变量仍在使用中,因此不是垃圾。如果 refcount
|
||
减少到 0,则 zval 可以释放。这意味着只有当引用计数参数减少到非零值时,才能创建垃圾循环。其次,在垃圾循环中,可以通过检查是否可以将 refcount 减少 1,并检查哪些
|
||
zval 的 refcount 为 0 来确定哪些部分是垃圾。
|
||
</para>
|
||
<para>
|
||
<mediaobject>
|
||
<alt>垃圾回收算法</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/gc-algorithm.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</para>
|
||
<para>
|
||
为避免不得不检查所有引用计数可能减少的垃圾循环,这个算法把所有可能根(possible roots 都是zval变量容器),放在根缓冲区(root buffer)中(用紫色来标记,称为疑似垃圾),这样可以同时确保每个可能的垃圾根(possible garbage root)在缓冲区中只出现一次。仅仅在根缓冲区满了时,才对缓冲区内部所有不同的变量容器执行垃圾回收操作。看上图的步骤 A。
|
||
</para>
|
||
<para>
|
||
在步骤 B 中,模拟删除每个紫色变量。模拟删除时可能将不是紫色的普通变量引用数减"1",如果某个普通变量引用计数变成0了,就对这个普通变量再做一次模拟删除。每个变量只能被模拟删除一次,模拟删除后标记为灰,确保不会对同一个变量容器减两次"1"。
|
||
</para>
|
||
<para>
|
||
在步骤 C 中,模拟恢复每个紫色变量。恢复是有条件的,当变量的引用计数大于0时才对其做模拟恢复。同样每个变量只能恢复一次,恢复后标记为黑,基本就是步骤 B 的逆运算。这样剩下的一堆没能恢复的就是该删除的蓝色节点了,在步骤 D 中遍历出来真的删除掉。
|
||
</para>
|
||
<para>
|
||
算法中都是模拟删除、模拟恢复、真的删除,都使用简单的遍历即可(最典型的深搜遍历)。复杂度为执行模拟操作的节点数正相关,不只是紫色的那些疑似垃圾变量。
|
||
</para>
|
||
<para>
|
||
对算法的工作原理有了基本的了解后,现在可以回顾一下如何与 PHP 集成。默认情况下,PHP 的垃圾回收器是打开的。然而,有个 &php.ini;
|
||
设置可以进行更改:<link linkend="ini.zend.enable-gc">zend.enable_gc</link>。
|
||
</para>
|
||
<para>
|
||
当打开垃圾回收器时,如上所述的循环查找算法将在根缓冲区满时执行。根缓冲区的大小是固定的,可以容纳 10,000 个可能的根(尽管可以通过更改
|
||
PHP 源代码中的 <literal>Zend/zend_gc.c</literal> 中的 <constant>GC_THRESHOLD_DEFAULT</constant> 常量并重新编译 PHP
|
||
来修改这个值)。当关闭垃圾回收器时,循环查找算法将永不运行。然而,无论是否使用此配置激活垃圾回收机制,可能根都将始终记录在根缓冲区中。
|
||
</para>
|
||
<para>
|
||
如果在垃圾回收机制关闭时,根缓冲区存满了可能的根,那么将不会记录进一步的可能根。算法永远不会分析那些没有记录的可能根。如果他们是循环引用的一部分,将永不会清除从而导致内存泄漏的产生。
|
||
</para>
|
||
<para>
|
||
即使在垃圾回收机制不可用时,可能根也被记录的原因是,相对于每次找到可能根后检查垃圾回收机制是否打开而言,记录可能根的操作更快。不过垃圾回收和分析机制本身要耗不少时间。
|
||
</para>
|
||
<para>
|
||
除了改变配置中的 <link linkend="ini.zend.enable-gc">zend.enable_gc</link> 之外,还可以通过调用 <function>gc_enable</function>
|
||
或 <function>gc_disable</function> 来启用/禁用垃圾回收机制。调用这些函数与通过配置打开或关闭机制的效果相同。即使可能的根缓冲区尚未满,还可以强制回收循环。为此,可以使用
|
||
<function>gc_collect_cycles</function> 函数。该函数将返回算法回收的循环数量。
|
||
</para>
|
||
<para>
|
||
允许打开和关闭垃圾回收机制并且允许自主的初始化的原因,是由于你的应用程序的某部分可能是高时效性的。在这种情况下,你可能不想使用垃圾回收机制。当然,对你的应用程序的某部分关闭垃圾回收机制,是在冒着可能内存泄漏的风险,因为一些可能根也许存不进有限的根缓冲区。因此,就在你调用<function>gc_disable</function>函数释放内存之前,先调用<function>gc_collect_cycles</function>函数可能比较明智。因为这将清除已存放在根缓冲区中的所有可能根,然后在垃圾回收机制被关闭时,可留下空缓冲区以有更多空间存储可能根。
|
||
</para>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="features.gc.performance-considerations">
|
||
<title>性能方面考虑的因素</title>
|
||
<para>
|
||
在上一节我们已经简单的提到:回收可能根有细微的性能上影响,但这是把PHP 5.2与PHP 5.3比较时才有的。尽管在PHP 5.2中,记录可能根相对于完全不记录可能根要慢些,而PHP 5.3中对 PHP run-time 的其他修改减少了这个性能损失。
|
||
</para>
|
||
<para>
|
||
这里主要有两个领域对性能有影响。第一个是内存占用空间的节省,另一个是垃圾回收机制执行内存清理时的执行时间增加(run-time delay)。我们将研究这两个领域。
|
||
</para>
|
||
|
||
<sect2 xml:id="features.gc.performance-considerations.reduced-mem">
|
||
<title>内存占用空间的节省</title>
|
||
<para>
|
||
首先,实现垃圾回收机制的整个原因是为了,一旦先决条件满足,通过清理循环引用的变量来节省内存占用。在PHP执行中,一旦根缓冲区满了或者调用<function>gc_collect_cycles</function> 函数时,就会执行垃圾回收。在下图中,显示了下面脚本分别在PHP 5.2 和 PHP 5.3环境下的内存占用情况,其中排除了脚本启动时PHP本身占用的基本内存。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Memory usage example</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class Foo
|
||
{
|
||
public $var = '3.14159265359';
|
||
public $self;
|
||
}
|
||
|
||
$baseMemory = memory_get_usage();
|
||
|
||
for ( $i = 0; $i <= 100000; $i++ )
|
||
{
|
||
$a = new Foo;
|
||
$a->self = $a;
|
||
if ( $i % 500 === 0 )
|
||
{
|
||
echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";
|
||
}
|
||
}
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
<mediaobject>
|
||
<alt>Comparison of memory usage between PHP 5.2 and PHP 5.3</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/gc-benchmark.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
在这个很理论性的例子中,我们创建了一个对象,这个对象中的一个属性被设置为指回对象本身。在循环的下一个重复(iteration)中,当脚本中的变量被重新复制时,就会发生典型性的内存泄漏。在这个例子中,两个变量容器是泄漏的(对象容器和属性容器),但是仅仅能找到一个可能根:就是被unset的那个变量。在10,000次重复后(也就产生总共10,000个可能根),当根缓冲区满时,就执行垃圾回收机制,并且释放那些关联的可能根的内存。这从PHP 5.3的锯齿型内存占用图中很容易就能看到。每次执行完10,000次重复后,执行垃圾回收,并释放相关的重复使用的引用变量。在这个例子中由于泄漏的数据结构非常简单,所以垃圾回收机制本身不必做太多工作。从这个图表中,你能看到 PHP 5.3的最大内存占用大概是9 Mb,而PHP 5.2的内存占用一直增加。
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="features.gc.performance-considerations.slowdowns">
|
||
<title>执行时间增加(Run-Time Slowdowns)</title>
|
||
<para>
|
||
垃圾回收影响性能的第二个领域是它释放已泄漏的内存耗费的时间。为了看到这个耗时时多少,我们稍微改变了上面的脚本,有更多次数的重复并且删除了循环中的内存占用计算,第二个脚本代码如下:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>GC性能影响</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class Foo
|
||
{
|
||
public $var = '3.14159265359';
|
||
public $self;
|
||
}
|
||
|
||
for ( $i = 0; $i <= 1000000; $i++ )
|
||
{
|
||
$a = new Foo;
|
||
$a->self = $a;
|
||
}
|
||
|
||
echo memory_get_peak_usage(), "\n";
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
运行这个脚本两次,一次将 <link linkend="ini.zend.enable-gc">zend.enable_gc</link> 设置打开,一次关闭。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>执行以上脚本</title>
|
||
<programlisting role="shell">
|
||
<![CDATA[
|
||
time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
|
||
# and
|
||
time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
在我的机器上,第一个命令持续执行时间大概为10.7秒,而第二个命令耗费11.4秒。时间上增加了7%。然而,执行这个脚本时内存占用的峰值降低了98%,从931Mb 降到 10Mb。这个基准不是很科学,或者并不能代表真实应用程序的数据,但是它的确显示了垃圾回收机制在内存占用方面的好处。好消息就是,对这个脚本而言,在执行中出现更多的循环引用变量时,内存节省的更多的情况下,每次时间增加的百分比都是7%。
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="features.gc.performance-considerations.internal-stats">
|
||
<title>PHP内部 GC 统计信息</title>
|
||
<para>
|
||
在PHP内部,可以显示更多的关于垃圾回收机制如何运行的信息。但是要显示这些信息,你需要先重新编译PHP使benchmark和data-collecting code可用。你需要在按照你的意愿运行<literal>./configure</literal>前,把环境变量<literal>CFLAGS</literal>设置成<literal>-DGC_BENCH=1</literal>。下面的命令串就是做这个事:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>重新编译PHP以启用GC benchmarking</title>
|
||
<programlisting role="shell">
|
||
<![CDATA[
|
||
export CFLAGS=-DGC_BENCH=1
|
||
./config.nice
|
||
make clean
|
||
make
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
当你用新编译的PHP二进制文件来重新执行上面的例子代码,在PHP执行结束后,你将看到下面的信息:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>GC 统计数据</title>
|
||
<programlisting role="shell">
|
||
<![CDATA[
|
||
GC Statistics
|
||
-------------
|
||
Runs: 110
|
||
Collected: 2072204
|
||
Root buffer length: 0
|
||
Root buffer peak: 10000
|
||
|
||
Possible Remove from Marked
|
||
Root Buffered buffer grey
|
||
-------- -------- ----------- ------
|
||
ZVAL 7175487 1491291 1241690 3611871
|
||
ZOBJ 28506264 1527980 677581 1025731
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
主要的信息统计在第一个块。你能看到垃圾回收机制运行了110次,而且在这110次运行中,总共有超过两百万的内存分配被释放。只要垃圾回收机制运行了至少一次,根缓冲区峰值(Root buffer peak)总是10000.
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="features.gc.performance-considerations.conclusion">
|
||
<title>结论</title>
|
||
<para>
|
||
通常,PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的(更小的)脚本中应根本就没有性能影响。
|
||
</para>
|
||
<para>
|
||
然而,在平常脚本中有循环回收机制运行的情况下,内存的节省将允许更多这种脚本同时运行在你的服务器上。因为总共使用的内存没达到上限。
|
||
</para>
|
||
<para>
|
||
这种好处在长时间运行脚本中尤其明显,诸如长时间的测试套件或者daemon脚本此类。同时,对通常比Web脚本运行时间长的<link xlink:href="&url.php.gtk;">PHP-GTK</link>应用程序,新的垃圾回收机制,应该会大大改变一直以来认为内存泄漏问题难以解决的看法。
|
||
</para>
|
||
</sect2>
|
||
</sect1>
|
||
</chapter>
|
||
|
||
<!-- Keep this comment at the end of the file
|
||
vim600: syn=xml fen fdm=syntax fdl=2 si
|
||
vim: et tw=78 syn=sgml
|
||
vi: ts=1 sw=1
|
||
-->
|