微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

php 迭代 dom 链接逐渐变慢

如何解决php 迭代 dom 链接逐渐变慢

好的,这是我的问题。我有一个 HTML,其中包含近 180,000 个链接。我正在创建一个 dom 以提取这些链接,然后将它们添加一个数组中以供以后处理。

我遇到的问题是,我对此做了一些研究,每次迭代时 foreach 都会逐渐变慢。我查看了 for 与 foreach 的比较,从我正在阅读的内容来看,这是最快的方法。就我而言,它仍然非常缓慢。

这是前 30,000 个链接的进度日志。前 5,000 次在大约一秒钟内完成。最后 1,000,10 秒。当它达到 80,000 时,完成 1,000 需要 30 秒。

2021-07-12 17:07:01 : 30000 links
2021-07-12 17:06:51 : 29000 links
2021-07-12 17:06:42 : 28000 links
2021-07-12 17:06:34 : 27000 links
2021-07-12 17:06:26 : 26000 links
2021-07-12 17:06:18 : 25000 links
2021-07-12 17:06:11 : 24000 links
2021-07-12 17:06:02 : 23000 links
2021-07-12 17:05:48 : 22000 links
2021-07-12 17:05:35 : 21000 links
2021-07-12 17:05:26 : 20000 links
2021-07-12 17:05:18 : 19000 links
2021-07-12 17:05:12 : 18000 links
2021-07-12 17:05:05 : 17000 links
2021-07-12 17:04:59 : 16000 links
2021-07-12 17:04:55 : 15000 links
2021-07-12 17:04:52 : 14000 links
2021-07-12 17:04:50 : 13000 links
2021-07-12 17:04:47 : 12000 links
2021-07-12 17:04:45 : 11000 links
2021-07-12 17:04:43 : 10000 links
2021-07-12 17:04:41 : 9000 links
2021-07-12 17:04:40 : 8000 links
2021-07-12 17:04:38 : 7000 links
2021-07-12 17:04:38 : 6000 links
2021-07-12 17:04:37 : 5000 links
2021-07-12 17:04:36 : 4000 links
2021-07-12 17:04:36 : 3000 links
2021-07-12 17:04:36 : 2000 links
2021-07-12 17:04:36 : 1000 links

我已经完成了 set_time_limit(1000) 并且在计时器用完之前还没有完成所有这些。

这是我拥有的似乎运行速度最快的代码。我也试过做一个 do/while 循环并取消设置对象的第一个元素。那是个坏主意。

请告诉我,有一种方法可以让 PHP 执行最后 1000 次与执行前 1000 次一样快。

        $content = $dom->getElementsByTagname('a');
        $count = 0;
        $this->write_log(count($content) . " total links");
        
        foreach ($content as $item) {
            $count++;
            if ($count % 1000 == 0) {
                $this->write_log($count . " links");
            }
            $item = rtrim($item->getAttribute('href'),'/');
            if (filter_var($item,FILTER_SANITIZE_URL)) {
                $this->DocInfo->links[] = $item;
            }
        }

谢谢

*用 cOle2 的代码编辑日志:

2021-07-12 18:35:14 : 26000 links
2021-07-12 18:35:03 : 25000 links
2021-07-12 18:34:53 : 24000 links
2021-07-12 18:34:41 : 23000 links
2021-07-12 18:34:29 : 22000 links
2021-07-12 18:34:21 : 21000 links
2021-07-12 18:34:14 : 20000 links
2021-07-12 18:34:05 : 19000 links
2021-07-12 18:33:56 : 18000 links
2021-07-12 18:33:47 : 17000 links
2021-07-12 18:33:39 : 16000 links
2021-07-12 18:33:34 : 15000 links
2021-07-12 18:33:31 : 14000 links
2021-07-12 18:33:25 : 13000 links
2021-07-12 18:33:22 : 12000 links
2021-07-12 18:33:18 : 11000 links
2021-07-12 18:33:16 : 10000 links
2021-07-12 18:33:14 : 9000 links
2021-07-12 18:33:12 : 8000 links
2021-07-12 18:33:11 : 7000 links
2021-07-12 18:33:11 : 6000 links
2021-07-12 18:33:10 : 5000 links
2021-07-12 18:33:10 : 4000 links
2021-07-12 18:33:10 : 3000 links
2021-07-12 18:33:10 : 2000 links
2021-07-12 18:33:09 : 1000 links

解决方法

处理具有固定大小的大型数组时,您可以使用 SplFixedArray,因为它比标准数组快一点。我认为您应该稍微调试一下时间的去向,但是以下内容可能值得尝试:

$content = $dom->getElementsByTagname('a');
$count = 0;
$this->write_log(count($content) . " total links");

$splArray = new SplFixedArray(count($content)); //create splfixed array

foreach ($content as $idx=>$item) { //track the index
    $count++;
    if ($count % 1000 == 0) {
        $this->write_log($count . " links");
    }
    $item = rtrim($item->getAttribute('href'),'/');
    if (filter_var($item,FILTER_SANITIZE_URL)) {
        $splArray[$idx] = $item; //assign item to fixed array position
    }
}

$this->DocInfo->links = $splArray->toArray(); //convert to a standard array
,

我发现了问题。它在于对象本身和 dom 的复杂性。经过一些测试,我得出了这个结论。下面是我使用的测试代码,您可以在此处运行它并亲自查看:https://sandbox.onlinephpfunctions.com/code/0a05fb4ad46a942a9d72cae51af819665904a037

我的发现:

在迭代大型对象时,对象比数组慢。对于一维数组和对象,差异并不那么显着,但很明显对象变慢了。当您开始添加键=> 值对时,对象真正引人注目的地方是。然而,与单维数组相比,具有键=> 值对的数组似乎不会遭受任何显着的速度损失。

在我的 OP 中,我使用了

$content = $dom->getElementsByTagname('a');

它创建了一个对象。但它不仅仅是链接的对象。我不熟悉 dom 的内部结构,但它似乎是一个非常复杂的对象,并且对其进行迭代消耗了大量时间。

我通过使用正则表达式而不是 dom 创建链接数组来确认这一点:

preg_match_all("/<a\s[^>]*href\s*=\s*([\"\']??)([^\"\' >]*?)\\1[^>]*>(.*)<\/a>/siU",$html,$content);

因为正则表达式让我感到困惑,而且我不太明白这是做什么的,所以这创建了一个多维数组,它比我需要的要多得多,但它完成了测试目的的工作。我将原始代码中的 $content 替换为数组中的 $content[2] 并更改了 rtrim 以删除 ->getAttribute。我能够在大约 5 秒内遍历 $content[2] 并处理所有 180,000 个链接,其中包括将日志写入 MySQL。

启发我的测试代码:

在这个测试代码中,我创建了一个哈希数组只是为了给它实体,然后将它复制到一个对象。然后我遍历两者并比较时间。然后我创建一个键-> 值对哈希数组并将其复制到一个对象。我遍历两者并比较时间。

我也尝试了这样的键=>值对:

foreach ($hashes as $key=>&$value) {

这最终使多维数组的速度减慢到甚至比对象版本还要慢,即使没有对值进行任何更改。

<?php
$hashes = array();
for ($x=0;$x<150000;$x++) {
    $hashes[] = md5(rand(1,1000));
}

function iterate($hashes) {
    $count = 0;
    $time_start = microtime(true);
    foreach ($hashes as $hash) {
        $count++;
        if ($count % 1000 == 0) {
            $time_end = microtime(true);
            $time = $time_end - $time_start;
            echo $count.' : '.$time.' : '."\n";
        }
    }    
}

$ohashes = (object)$hashes;
iterate($ohashes);
echo 'Done with single object'."\n";
echo "\n";
iterate($hashes);
echo 'Done with single array'."\n";
echo "\n";
echo "\n";
unset($hashes);
unset($ohashes);

$hashes = array();
for ($x=0;$x<150000;$x++) {
    $kv = md5($x);
    $hashes[$kv] = $kv;
}

function iterate2($hashes) {
    $count = 0;
    $time_start = microtime(true);
    foreach ($hashes as $key=>$value) {
        $count++;
        if ($count % 1000 == 0) {
            $time_end = microtime(true);
            $time = $time_end - $time_start;
            echo $count.' : '.$time.' : '."\n";
        }
    }    
}

$ohashes = (object)$hashes;
iterate2($ohashes);
echo 'Done with multi object'."\n";
echo "\n";
iterate2($hashes);
echo 'Done with multi array'."\n";

我从中得到了什么:

所以,如果我的测试代码是准确的并且我正确读取了它的结果,那么在迭代时数组比对象更快。对象越复杂,它相对于可比较数组的速度就越慢。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。