用PHP释放内存的更好之处是:unset()或$ var = null


244

我意识到第二个方法避免了函数调用(update,实际上是一种语言构造)的开销,但是知道一个方法是否比另一个更好会很有趣。我一直在使用unset()大多数编码,但是最近我浏览了一些在网上发现的可$var = null替代的可敬类。

有没有首选的,这是什么原因?

Answers:


234

在2009年未设置的手册页中提到:

unset()确实按照其名称所说-取消设置变量。它不会强制立即释放内存。PHP的垃圾收集器将在其认为合适的情况下执行此操作-出于故意,因为无论如何都不需要这些CPU周期,或者直到脚本耗尽内存之前(无论哪种情况先发生)。

如果这样做,$whatever = null;则将重写变量的数据。您可能会更快地释放/缩小内存,但可能会从真正需要它们的代码中窃取CPU周期,从而导致更长的总体执行时间。

(自2013年以来,该unset手册页不再包含该部分)

请注意,直到php5.3,如果您有两个循环引用的对象(例如,父子关系),则在父对象上调用unset()不会释放子对象中用于父引用的内存。(当父对象被垃圾回收时,也不会释放内存。)(bug 33595


问题“ unset和= null之间的差异 ”详细说明了一些差异:


unset($a)$a从符号表中删除;例如:

$a = str_repeat('hello world ', 100);
unset($a);
var_dump($a);

输出:

Notice: Undefined variable: a in xxx
NULL

但是何时$a = null使用:

$a = str_repeat('hello world ', 100);
$a = null;
var_dump($a);
Outputs:

NULL

似乎这$a = null比它的unset()对应对象要快一些:更新符号表条目似乎比删除它快。


  • 当您尝试使用不存在的(unset)变量时,将触发错误,并且变量表达式的值将为null。(因为,PHP还应该做什么?每个表达式都需要产生一些值。)
  • 分配了null的变量仍然是完全正常的变量。

18
请注意,如果$whatever指向对象,则$whatever = null覆盖指针,而不是对象本身,因此它的行为与基本上相同unset()
Gras Double

1
@VonC:您所引用的php.net上未设置的报价不再存在。
尔根·塞伦(JürgenThelen),

@JürgenThelen是真的,但是那个旧答案的内容似乎仍然有意义,不是吗?
VonC

1
@VonC:当然。我只是不确定“不需要CPU周期”和“内存不足之前”触发垃圾回收。请参阅stackoverflow.com/q/20230626/693207。也许您可以阐明一些想法?
尔根·塞伦(JürgenThelen),

1
@Omar我已经编辑了答案:2009年未设置的手册页(我已链接到2009版)确实包含一个部分,该部分不再存在于同一页面的当前版本中。
VonC

48

unset实际上不是一个函数,而是一种语言构造。它只不过是a return或an 函数调用include

除了性能问题外,使用还unset可以使您的代码意图更加清晰。


这就是为什么我一直使用它们的原因,我个人认为它们看起来比$ var = null好。顺便说一句,我一直使用NULL全大写...但是现在我不知道为什么?
亚历克斯

1
@VonC:是的,我知道了,但是为什么要使用小写的true,false和null?
亚历克斯

3
@alex,您可以在未设置的情况下执行此操作。例如“ $ test = 4;(未设置)$ test;” -奇怪但正确,它在取消设置之前返回$ test的值。无论如何,PHP手册确实确认它是一种语言构造。
thomasrutter,2009年

5
@alex:PSR-2 的所有关键字均要求小写。
Tgr 2012年

2
@alex-PHP关键字不区分大小写;例如,您也可以拼写unsetUnSeT。社区已将全小写字母定为样式,但其他大小写仍然可用。
马克·里德

35

通过对变量执行unset(),您实际上已将变量标记为“垃圾回收”(PHP确实没有一个,但例如),因此内存不会立即可用。该变量不再容纳数据,但堆栈保持较大大小。使用null方法会删除数据并几乎立即缩小堆栈内存。

这是根据个人经验和其他经验得出的。在此处查看unset()函数的注释。

我个人在循环中的两次迭代之间使用unset(),这样我就不必使堆栈的延迟大小溜溜溜溜了。数据不见了,但是占用空间仍然存在。在下一次迭代中,内存已经被php占用,因此可以更快地初始化下一个变量。


15
如果保存值NULL所需的内存小于保存它以前保存的任何值所需的内存,则将某些内容设置为NULL可能会有所帮助。例如,长字符串。如果字符串不是常量,并且其引用计数降至零,则应释放该内存。重置更干净-不再维护参考。您确实需要等待垃圾收集,但是将其视为不占用内存是安全的,因为内存不足的情况将触发垃圾收集。
thomasrutter,2009年

我们不能同时使用两者吗?等于null然后未设置?
纳比尔·汗

2
@NabeelKhan我建议在循环内使用unset(),然后在退出循环时使其无效。否则,在循环中进行这两个操作都会对性能产生影响。如果您不使用循环,则只需作废即可,因为它已经在后台执行了unset()逻辑。
William Holroyd

27
<?php
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    $a = 'a';
    $a = NULL;
}
$elapsed = microtime(true) - $start;

echo "took $elapsed seconds\r\n";



$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    $a = 'a';
    unset($a);
}
$elapsed = microtime(true) - $start;

echo "took $elapsed seconds\r\n";
?>

每看来“ = null”似乎更快。

PHP 5.4结果:

  • 花了0.88389301300049秒
  • 花了2.1757180690765秒

PHP 5.3结果:

  • 花了1.7235369682312秒
  • 花了2.9490959644318秒

PHP 5.2结果:

  • 花了3.0069220066071秒
  • 花了4.7002630233765秒

PHP 5.1结果:

  • 花了2.6272349357605秒
  • 花了5.0403649806976秒

PHP 5.0和4.4开始看起来有所不同。

5.0:

  • 花了10.038941144943秒
  • 花了7.0874409675598秒

4.4:

  • 花了7.5352551937103秒
  • 花了6.6245851516724秒

请记住,microtime(true)在PHP 4.4中不起作用,因此我不得不使用php.net/microtime / Example#1中给出的microtime_float示例。


7
我认为您的测试有缺陷。第一个循环是简单的重新分配,第二个循环销毁并重新创建相同的符号。如果使用阵列重做测试,unset则速度更快。我有一个测试,以后会检查该unset案例是否存在。在该测试中,将其设置为要null快一些。测试:pastebin.com/fUe57C51
Knyri

4
@ansur,请始终gc_collect_cycles在启动计时器之前致电以获得更准确的结果。
Pacerier,2013年

@knyri,请您链接到它吗?
纳比尔·汗

@NabeelKhan我不再拥有该测试的结果;但在我之前的评论中有指向测试代码的链接。
克妮里

19

它与数组元素有所不同。

考虑这个例子

$a = array('test' => 1);
$a['test'] = NULL;
echo "Key test ", array_key_exists('test', $a)? "exists": "does not exist";

在这里,关键的“测试”仍然存在。但是,在这个例子中

$a = array('test' => 1);
unset($a['test']);
echo "Key test ", array_key_exists('test', $a)? "exists": "does not exist";

密钥不再存在。


18

对于通过引用复制的变量,它以不同的方式工作:

$a = 5;
$b = &$a;
unset($b); // just say $b should not point to any variable
print $a; // 5

$a = 5;
$b = &$a;
$b = null; // rewrites value of $b (and $a)
print $a; // nothing, because $a = null

5
我已经编码php几年了,从未见过关于引用原始var的“&”。谢谢+ 1 :)
克里斯

1
$ a = 78; $ b = $ a; 未设置($ a); var_dump($ b); // 78; var_dump($ a); //未定义的变量:a
zloctb 2013年

13

对于对象,尤其是在延迟加载情况下,应该考虑垃圾收集器正在空闲的CPU周期中运行,因此,假设许多对象正在加载时耗费小时间,就会陷入麻烦,这将解决内存释放问题。

使用time_nanosleep使GC能够收集内存。将变量设置为null是可取的。

在生产服务器上进行了测试,该作业最初消耗了50MB,然后被暂停。使用nanosleep后,14MB的内存消耗是恒定的。

应该说这取决于GC行为,该行为可能随PHP版本的不同而变化。但是它可以在PHP 5.3上正常工作。

例如。此示例(代码取自VirtueMart2 Google Feed)

for($n=0; $n<count($ids); $n++)
{
    //unset($product); //usefull for arrays
    $product = null
    if( $n % 50 == 0 )
    {
        // let GC do the memory job
        //echo "<mem>" . memory_get_usage() . "</mem>";//$ids[$n];
        time_nanosleep(0, 10000000);
    }

    $product = $productModel->getProductSingle((int)$ids[$n],true, true, true);
    ...

3

我仍然对此表示怀疑,但是我已经在脚本中进行了尝试,并且正在使用xdebug来了解它会如何影响应用程序的内存使用。脚本是在我的函数上这样设置的:

function gen_table_data($serv, $coorp, $type, $showSql = FALSE, $table = 'ireg_idnts') {
    $sql = "SELECT COUNT(`operator`) `operator` FROM $table WHERE $serv = '$coorp'";
    if($showSql === FALSE) {
        $sql = mysql_query($sql) or die(mysql_error());
        $data = mysql_fetch_array($sql);
        return $data[0];
    } else echo $sql;
}

我在return代码之前添加unset ,它给了我:160200然后我尝试用$sql = NULL它来更改它,它给了我:160224 :)

但是当我不使用unset()或NULL时,此比较项有一些独特之处,xdebug给我160144作为内存使用量

因此,我认为让行使用unset()或NULL将为您的应用程序添加过程,并且最好使代码保持原点,并尽可能减少使用的变量。

纠正我,如果我错了,谢谢


我想当您返回$ data [0]项时,整个数组都被引用了/但这只是假设。尝试将$ data [0]复制到本地变量,将数组设置为null并返回本地变量。良好的背景是这里tuxradar.com/practicalphp/18/1/11和ofc。php.net/manual/en/features.gc.php
OSP

2

我为unset和创建了一个新的性能测试=null,因为如注释中所述,此处编写的内容存在错误(重新创建元素)。我使用了数组,如您所见,现在已经无关紧要了。

<?php
$arr1 = array();
$arr2 = array();
for ($i = 0; $i < 10000000; $i++) {
    $arr1[$i] = 'a';
    $arr2[$i] = 'a';
}

$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    $arr1[$i] = null;
}
$elapsed = microtime(true) - $start;

echo 'took '. $elapsed .'seconds<br>';

$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    unset($arr2[$i]);
}
$elapsed = microtime(true) - $start;

echo 'took '. $elapsed .'seconds<br>';

但是我只能在PHP 5.5.9服务器上对其进行测试,结果如下:-花了4.4571571350098秒-花了4.4425978660583秒

unset由于可读性原因,我更喜欢。


2

PHP 7已经可以解决此类内存管理问题,并将其减少到最低限度使用。

<?php
  $start = microtime(true);
  for ($i = 0; $i < 10000000; $i++) {
    $a = 'a';
    $a = NULL;
  }
  $elapsed = microtime(true) - $start;

  echo "took $elapsed seconds\r\n";

  $start = microtime(true);
  for ($i = 0; $i < 10000000; $i++) {
     $a = 'a';
     unset($a);
  }
  $elapsed = microtime(true) - $start;

  echo "took $elapsed seconds\r\n";

?>

PHP 7.1 Outpu:

花了0.16778993606567秒花了0.16630101203918秒


1

unset如果不释放立即内存,那么代码仍然非常有帮助,并且这是一个好的习惯,每次我们在退出方法之前传递代码步骤时,都要这样做。请注意其与释放立即内存无关。立即内存用于CPU,而辅助内存(RAM)呢?

这也解决了防止内存泄漏的问题。

请参阅此链接 http://www.hackingwithphp.com/18/1/11/be-wary-of-garbage-collection-part-2

我已经使用unset很长时间了。

更好的做法是在代码中暂时取消设置所有已经用作数组的变量。

$data['tesst']='';
$data['test2']='asdadsa';
....
nth.

just unset($data);释放所有可变用法。

请查看相关主题以取消设置

在PHP中取消设置变量有多重要?

[臭虫]


1

作记录,并排除花费的时间:

<?php
echo "<hr>First:<br>";
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n"; 
echo "<hr>Unset:<br>";
unset($x);
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n"; 
echo "<hr>Null:<br>";
$x=null;
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n";

echo "<hr>function:<br>";
function test() {
    $x = str_repeat('x', 80000);
}
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n"; 

echo "<hr>Reasign:<br>";
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n"; 

它返回

First:
438296
438352
Unset:
438296
438352
Null:
438296
438352
function:
438296
438352
Reasign:
438296
520216 <-- double usage.

结论,空内存和未设置的空闲内存均符合预期(不仅在执行结束时)。同样,重新分配变量会将值保留两次(520216与438352)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.