【问题标题】:php iterating dom links progressively slowerphp迭代dom链接逐渐变慢
【发布时间】:2025-12-08 10:40:01
【问题描述】:

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

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

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

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

【问题讨论】:

  • 去掉日志部分会不会更快?
  • 我已经玩了一点,因为它确实将日志写入 MySQL 表。但即使我将模数设置为 10,000,它仍然会越来越慢。我在每次运行之前清空表,日志功能只是一个简单的插入。
  • 尝试只是迭代,不要做任何工作,虽然你仍然可以登录,看看是否更快
  • 是的,现在我很困惑。我按照建议禁用了这项工作。逐渐变慢。我禁用了日志记录,而只是回显了日期和计数。还是越来越慢。这是 wp 插件的一部分,所以我不确定 wp 是否会减慢此输出。我正在使用 JS 来显示日志。把它剪掉,看看有没有效果。谢谢!
  • 禁用了我的 js,只是回显了时间和日期,仍然越来越慢。

标签: php for-loop dom foreach


【解决方案1】:

在处理具有固定大小的大型数组时,您可以使用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

【讨论】:

  • 刚刚将输出日志添加到我用你的代码替换我所拥有的操作的操作中。好像慢了一点。甚至没有达到 30,000 个链接。
  • SplFixedArray used某些 情况下更快。在最近对数组进行优化之后,现在通常是性能惩罚
【解决方案2】:

我发现了问题。它存在于对象本身和 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。我能够遍历 $content[2] 并在大约 5 秒内处理所有 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";

我从中得到的:

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

【讨论】: