Preg_replace的效率

时间:2010-08-20 02:24:05

标签: php regex loops performance preg-replace

执行摘要:

preg_replace()比字符串比较运行得更快。为什么?正则表达式不应该更慢吗?


recent question中有关检测给定输入中任何一系列不允许的子字符串的问题,我建议将preg_replace()调用的结果与原始输入进行比较,因为preg_replace()可以采用一组模式作为输入。因此,我的方法可以是单个if,而其他解决方案需要一个(或多个)循环。

我对辩论我的答案并不感兴趣,因为它实际上比循环更不易读/维护。我的答案仍然保持-1,我会接受为了可读性/易于维护,但我的方法指出的最大错误是缺乏效率。这让我很好奇,并让我做了一些测试。我的结果对我来说有点令人惊讶:在所有其他因素保持不变的情况下,preg_replace()比其他任何方法都快

你能解释为什么会这样吗?

我可以在下面找到这些测试的代码以及结果:

$input = "In a recent question about detecting any of an array of disallowed substrings within a given input, I suggested comparing the result of a `preg_replace()` call to the original input, since `preg_replace()` can take an array of patterns as input. Thus my method for this could be a single `if` whereas the other solutions required one (or many) loops. I'm not interested in debating my answer, because really it is less readable/maintainable than the loops. However, the biggest fault pointed out with my method was a lack of efficiency. That got me curious, and led me to do some testing. My results were a bit surprising to me: with all other factors held equal, `preg_replace()` was **faster** than any of the other methods. Can you explain why this was the case?";
$input2 = "Short sentence - no matches";
$input3 = "Word";
$input4 = "Short sentence - matches loop";

$start1 = microtime(true);
$rejectedStrs = array("loop", "efficiency", "explain");
$p_matches = 0;
for ($i = 0; $i < 10000; $i++) {
    if (str_check($rejectedStrs, $input)) $p_matches++;
    if (str_check($rejectedStrs, $input2)) $p_matches++;
    if (str_check($rejectedStrs, $input3)) $p_matches++;
    if (str_check($rejectedStrs, $input4)) $p_matches++;
}

$start2 = microtime(true);
$rejectedStrs = array("loop", "efficiency", "explain");
$l_matches = 0;
for ($i = 0; $i < 10000; $i++) {
    if (loop_check($rejectedStrs, $input)) $l_matches++;
    if (loop_check($rejectedStrs, $input2)) $l_matches++;
    if (loop_check($rejectedStrs, $input3)) $l_matches++;
    if (loop_check($rejectedStrs, $input4)) $l_matches++;
}

$start3 = microtime(true);
$rejectedStrs = array("/loop/", "/efficiency/", "/explain/");
$s_matches = 0;
for ($i = 0; $i < 10000; $i++) {
    if (preg_check($rejectedStrs, $input)) $s_matches++;
    if (preg_check($rejectedStrs, $input2)) $s_matches++;
    if (preg_check($rejectedStrs, $input3)) $s_matches++;
    if (preg_check($rejectedStrs, $input4)) $s_matches++;
}

$end = microtime(true);
echo $p_matches." ".$l_matches." ".$s_matches."\n";
echo "str_match: ".$start1." ".$start2."= ".($start2-$start1)."\nloop_match: ".$start2." ".$start3."=".($start3-$start2)."\npreg_match: ".$start3." ".$end."=".($end-$start3);

function preg_check($rejectedStrs, $input) {
    if($input == preg_replace($rejectedStrs, "", $input)) 
        return true;
    return false;
}

function loop_check($badwords, $string) {

    foreach (str_word_count($string, 1) as $word) {
        foreach ($badwords as $bw) {
            if (stripos($word, $bw) === 0) {
                return false;
            }
        }
    }
    return true;
}

function str_check($badwords, $str) {
    foreach ($badwords as $word) {
        if (stripos(" $str ", " $word ") !== false) {
            return false;
        }
    }
    return true;
}

结果

  

20000 20000 20000

     

str_match:1282270516.6934 1282270518.5881 = 1.894730091095

     

loop_match:1282270518.5881 1282270523.0943 = 4.5061857700348

     

preg_match:1282270523.0943 1282270523.6191 = 0.52475500106812

2 个答案:

答案 0 :(得分:2)

  

你能解释为什么会这样吗?

易。 preg_match在C中实现。其他解决方案在PHP中实现。现在,这并不意味着正则表达式总是比同等的PHP更快,但大多数时候,它可能会更快。

我最近有类似的情况,我有一个函数(一个CamelCase转换器),被称为数千次,并占用了相当数量的CPU(我描述)。我尝试了所有可以实现的PHP重新实现。 preg_replace总是更快。最后,我按原样离开了这个功能,并记住它,这就是诀窍。

在许多情况下,执行的PHP语句越少越好。如果您可以通过一次调用替换一个循环来实现在C中实现的功能,那么这可能是您最好的选择。

  

实际上它比循环

更不易读/维护

我不同意。 One-liners就像它一样简单。虽然我可能会更喜欢

function preg_check($rejectedStrs, $input) {
    return preg_match($rejectedStrs, "", $input);
}

答案 1 :(得分:2)

我们先来看看preg_checkloop_check。它们都必须遍历整个字符串,并且必须检查每次遍历中的每个单词。所以他们的行为至少是O(n*m),其中n是字符串的长度,m是坏字的数量。您可以通过使用增加nm的值来运行算法并绘制3D图表来测试这一点(但是,您可能或可能不会以非常高的{{1}值运行它和} n看到这种行为。)

这里

m 更多(渐近)效率。原因是字符串所具有的单词数与其长度不成比例 - 我似乎记得它通常遵循对数函数。它可能使用哈希表来存储它通过该方式找到的单词,这是在平均恒定时间内完成的(如果我们忽略了我们可能不时需要重建哈希表以容纳更多元素)。

因此,loop_check会有类似loop_check之类的渐近行为,优于n + m * log(n)

现在,这指的是算法的渐近行为,即当n*mm非常大(并且可能需要“非常非常”)大时。对于nm的小值,常量起很大作用。特别是,PHP操作码和PHP函数调用的执行比在C中实现的相同任务更昂贵,只需要一个函数调用。这不会使正则表达式算法更快,只会使nm的小值更快。