【问题标题】:Memory leakage in php with three for loopsphp中的内存泄漏与三个for循环
【发布时间】:2012-01-27 19:19:59
【问题描述】:

我的脚本是一个蜘蛛,它检查页面是“链接页面”还是“信息页面”。 如果页面是“链接页面”,那么它会以递归方式继续(如果你愿意,也可以是树) 直到找到“信息页”。

我试图让脚本递归,这很容易,但我一直收到错误:

致命错误:允许的内存大小为 33554432 字节已用尽(试图 在第 1316 行的 /srv/www/loko/simple_html_dom.php 中分配 39 个字节)

有人告诉我,我必须使用 for 循环方法,因为无论我是否使用 unset() 函数,脚本都不会释放内存,而且我只需要循环三个级别通过所以这是有道理的。但是在我更改脚本后,错误再次发生,但也许我可以释放 现在还记得吗?

这里有东西要死,请帮我毁掉一个人!

set_time_limit(0);
ini_set('memory_limit', '256M');
require("simple_html_dom.php");
$thelink = "http://www.somelink.com";
$html1 = file_get_html($thelink);
$ret1 = $html1->find('#idTabResults2');

// first inception level, we know page has only links
if (!$ret1){
    $es1 = $html1->find('table.litab a');
    //unset($html1);
    $countlinks1 = 0;
    foreach ($es1 as $aa1) {
        $links1[$countlinks1] = $aa1->href;
        $countlinks1++;
    }
    //unset($es1);

    //for every link in array do the same
    for ($i = 0; $i < $countlinks1; $i++) {
        $html2 = file_get_html($links1[$i]);
        $ret2 = $html2->find('#idTabResults2');
        // if got information then send to DB
        if ($ret2){
            pullInfo($html2);
            //unset($html2);
        } else {
        // continue inception
            $es2 = $html2->find('table.litab a');
            $html2 = null;

            $countlinks2 = 0;
            foreach ($es2 as $aa2) {
            $links2[$countlinks2] = $aa2->href;
            $countlinks2++;
            }
            //unset($es2);

            for ($j = 0; $j < $countlinks2; $j++) {
                $html3 = file_get_html($links2[$j]);
                $ret3 = $html3->find('#idTabResults2');
                // if got information then send to DB       
                if ($ret3){
                    pullInfo($html3);

                } else {
                // inception level three
                    $es3 = $html3->find('table.litab a');
                    $html3 = null;
                    $countlinks3 = 0;
                    foreach ($es3 as $aa3) {
                        $links3[$countlinks3] = $aa3->href;
                        $countlinks3++;
                    }
                    for ($k = 0; $k < $countlinks3; $k++) {
                        echo memory_get_usage() ;
                        echo "\n";
                        $html4 = file_get_html($links3[$k]);
                        $ret4 = $html4->find('#idTabResults2');
                        // if got information then send to DB       
                        if ($ret4){
                            pullInfo($html4);

                        }
                        unset($html4);                  
                    }
                    unset($html3);
                }

            }
        }
    }
}



function pullInfo($html)
{

$tds = $html->find('td');
$count =0; 
foreach ($tds as $td) {
  $count++;
  if ($count==1){
    $name = html_entity_decode($td->innertext);
   }
  if ($count==2){
        $address = addslashes(html_entity_decode($td->innertext));
   }
  if ($count==3){
    $number = addslashes(preg_replace('/(\d+) - (\d+)/i', '$2$1', $td->innertext));
   }

}
unset($tds, $td);

$name = mysql_real_escape_string($name);
$address = mysql_real_escape_string($address);
$number = mysql_real_escape_string($number);
$inAlready=mysql_query("SELECT * FROM people WHERE phone=$number");
while($e=mysql_fetch_assoc($inAlready))
            $output[]=$e;
    if (json_encode($output) != "null"){ 
        //print(json_encode($output));
    } else {

mysql_query("INSERT INTO people (name, area, phone)
VALUES ('$name', '$address', '$number')");
}
}

这是内存大小增长的图片:

【问题讨论】:

  • require("simple_html_dom.php");会不会在这里发生?
  • @NSjonas 你是什么意思?和 RPM 我在 bash 中运行它
  • 顺便说一下,您的 SQL 查询容易受到 SQL 注入的攻击。
  • 请注意,您可以将 $data1 = file_get_contents($thelink); $html1 = str_get_html($data1); 替换为 $html1 = file_get_html($thelink);。这样可以避免在内存中保留每个文档的多个表示形式。
  • 您可能还想在使用完这三个文档后分别释放它们。

标签: php memory memory-leaks memory-management


【解决方案1】:

我稍微修改了代码以释放我看到的尽可能多的内存。 我在每个修改上方添加了评论。添加的 cmets 以“#”开头,因此您可以更轻松地找到它们。 这与这个问题无关,但值得一提的是,您的数据库插入代码容易受到 SQL 注入的影响。

<?php
require("simple_html_dom.php");
$thelink = "http://www.somelink.co.uk";

# do not keep raw contents of the file on memory
#$data1 = file_get_contents($thelink);
#$html1 = str_get_html($data1);
$html1 = str_get_html(file_get_contents($thelink));

$ret1 = $html1->find('#idResults2');

// first inception level, we know page has only links
if (!$ret1){
    $es1 = $html1->find('table.litab a');

    # free $html1, not used anymore
    unset($html1);

    $countlinks1 = 0;
    foreach ($es1 as $aa1) {
        $links1[$countlinks1] = $aa1->href;
        $countlinks1++;
        // echo (addslashes($aa->href));
    }

    # free memroy used by the $es1 value, not used anymore
    unset($es1);

    //for every link in array do the same

    for ($i = 0; $i <= $countlinks1; $i++) {
        # do not keep raw contents of the file on memory
        #$data2 = file_get_contents($links1[$i]);
        #$html2 = str_get_html($data2);
        $html2 = str_get_html(file_get_contents($links1[$i]));

        $ret2 = $html2->find('#idResults2');

        // if got information then send to DB
        if ($ret2){
            pullInfo($html2);
        } else {
        // continue inception

            $es2 = $html2->find('table.litab a');

            # free memory used by $html2, not used anymore.
            # we would unset it at the end of the loop.
            $html2 = null;

            $countlinks2 = 0;
            foreach ($es2 as $aa2) {
                $links2[$countlinks2] = $aa2->href;
                $countlinks2++;
            }

            # free memory used by $es2
            unest($es2);

            for ($j = 0; $j <= $countlinks2; $j++) {
                # do not keep raw contents of the file on memory
                #$data3 = file_get_contents($links2[$j]);
                #$html3 = str_get_html($data3);
                $html3 = str_get_html(file_get_contents($links2[$j]));
                $ret3 = $html3->find('#idResults2');
                // if got information then send to DB   
                if ($ret3){
                    pullInfo($html3);
                }

                # free memory used by $html3 or on last iteration the memeory would net get free
                unset($html3);
            }
        }

        # free memory used by $html2 or on last iteration the memeory would net get free
        unset($html2);
    }
}



function pullInfo($html)
{
    $tds = $html->find('td');
    $count =0; 
    foreach ($tds as $td) {
      $count++;
      if ($count==1){
        $name = addslashes($td->innertext);
       }
      if ($count==2){
            $address = addslashes($td->innertext);
       }
      if ($count==3){
        $number = addslashes(preg_replace('/(\d+) - (\d+)/i', '$2$1', $td->innertext));
       }

    }

    # check for available data:
    if ($count) {
        # free $tds and $td
        unset($tds, $td);

        mysql_query("INSERT INTO people (name, area, phone)
        VALUES ('$name', '$address', '$number')");
    }

}

更新:

您可以跟踪您的内存使用情况,以查看代码的每个部分中使用了多少内存。这可以通过使用 memory_get_usage() 调用并将结果保存到某个文件来完成。就像将下面的代码放在每个循环的末尾,或者在创建对象之前调用繁重的方法:

file_put_contents('memory.log', 'memory used in line ' . __LINE__ . ' is: ' . memory_get_usage() . PHP_EOL, FILE_APPEND);

因此您可以跟踪代码的每个部分的内存使用情况。

最后请记住,所有这些跟踪和优化可能还不够,因为您的应用程序可能确实需要超过 32 MB 的内存。我开发了一个系统,可以分析多个数据源并检测垃圾邮件发送者,然后阻止他们的 SMTP 连接,由于有时连接的用户数超过 30000,经过大量代码优化后,我不得不将 PHP 内存限制增加到 768服务器上的 MB,这不是常见的事情。

【讨论】:

  • 仍然在同一个地方失败:/ ohh
  • 致命错误:/srv/www/loko/simple_html_dom.php 第 1288 行中允许的内存大小为 33554432 字节已用尽(尝试分配 39 字节)
  • @Tom:simple_html_dom.php 文件呢?它是一个写得很好的图书馆吗?有没有其他图书馆可以帮助你?我建议您在脚本中放置一些 memory_get_usage() 调用并将它们存储在某个文件中以进行调试并找到内存使用量上升的位置。那是您应该优化代码的地方。最后,您可能需要更改应用程序结构以避免嵌套循环。
  • 这是一个众所周知的库 simplehtmldom.sourceforge.net 我正在尝试使用 memory_get_usage()。我修改了我的代码。
  • @Tom:更新了描述更多关于跟踪内存使用情况的答案。
【解决方案2】:

如果您的操作需要内存并且您的服务器有更多可用内存,您可以调用ini_set('memory_limit', '128M'); 或类似的方式(取决于您的内存要求)来增加脚本可用的内存量。

这并不意味着您不应该优化和重构您的代码 :-) 这只是其中的一部分。

【讨论】:

  • 内存泄漏呈指数级增长,因此以这种方式增加内存将无济于事:/感谢您的建议,我在途中学到了其他东西:)
  • 我认为这是不受控制的使用,而不是泄漏-您是在告诉脚本获取所有链接并在每个链接上生成 HTML 对象,然后再次执行相同操作。因此,您应该重构并将此代码放在一个函数中,您可以在完成后调用并取消设置对象。取消设置将释放内存,尽管不会立即释放。
  • 我添加了 ini_set('memory_limit', '256M');它又运行了一分钟左右,但在内存达到 256MB 时失败了。这只是它必须走的路的1/9。这太疯狂了.. 256MB 用于存储链接(字符串)?这是怎么回事?
【解决方案3】:

解决方案是使用 clear 方法,例如: $html4->clear();清除内存的 simple_html_dom 方法 当您完成 DOM 元素时。 想了解更多,请输入this website

【讨论】:

    【解决方案4】:

    首先,让我们把它变成一个真正的递归函数,这样可以更容易地修改整个事件链:

    function findInfo($thelink)
    {
        $data = file_get_contents($thelink);  //Might want to make sure that it's a valid link, i.e. that file get contents actually returned stuff, before trying to run further with it.
        $html = str_get_html($data);
    
        unset($data);  //Finished using it, no reason to keep it around.
    
        $ret = $html->find('#idResults2');
    
        if($ret)
        {
             pullInfo($html);
             return true;    //Should stop once it finds it right?
        }
        else
        {
             $es = $html->find('table.litab a');  //Might want a little error checking here to make sure it actually found links.
    
             unset($html); //Finished using it, no reason to keep it around
    
             $countlinks = 0;
    
             foreach($es as $aa)
             {
                  $links[$countlinks] = $aa->href;
                  $countlinks++;
             }
    
             unset($es);  //Finished using it, no reason to keep it around.
    
             for($i = 0; $i <= $countlinks; $i++)
             {
                  $result = findInfo($links[$i]);
    
                  if($result === true)
                  {
                      return true;  //To break out of above recursive functions if lower functions return true
                  }
                  else
                  {
                      unset($links[$i]); //Finished using it, no reason to keep it around.
                      continue;
                  }
             }     
        }
        return false;  //Will return false if all else failed, should hit a return true before this point if it successfully finds an info page.
    }
    

    看看这是否有助于清理。可能仍然内存不足,但您不应该保留扫描的每个网页的完整 html 以及这不是什么。

    哦,如果你只希望它走得这么深,请将函数声明更改为:

    function findInfo($thelink, $depth = 1, $maxdepth = 3)
    

    那么在函数内部调用函数时,这样调用:

    findInfo($html, $depth + 1, $maxdepth); //you include maxdepth so you can override it in the initial function call, like findInfo($thelink,,4)
    

    然后在函数开始时检查深度与最大深度,如果深度为&gt; 而不是最大深度,则使用return false

    【讨论】:

    • 哇,Phonix 为您付出了多么大的努力!我也会试试这个,看看会发生什么
    • @Tom 因为在包含的库中执行某些功能时它似乎失败了,所以我要做的下一件事是将它固定到它失败的确切位置。在使用库中的每个函数之前添加控制台或日志输出,例如 str_get_html-&gt;find。也有可能它在pullInfo 中失败了,也许页面上的 td 太多以至于它占用了所有内存来试图找到它们。永远不知道。
    【解决方案5】:

    如果内存使用是您最关心的问题,您可能需要考虑使用基于 SAX 的解析器。使用 SAX 解析器进行编码可能会稍微复杂一些,但没有必要将整个文档保存在内存中。

    【讨论】:

    • 我会研究一下 Michael,现在我正在执行您上面的建议,看看它是否有帮助。
    猜你喜欢
    • 2017-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-28
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    • 2017-08-13
    相关资源
    最近更新 更多