【问题标题】:PHP regular expression optimizationPHP正则表达式优化
【发布时间】:2017-12-12 21:00:23
【问题描述】:

我正在尝试优化 PHP 正则表达式,并正在向优秀的 Stack Overflow 社区寻求指导。

我正在尝试在 HTML 块中捕获预定义的匹配项,例如:

##test##

##!test2##

##test3|id=5##

将运行的示例文本是:

Lorem ipsum dolor sit amet,##test## consectetur adipiscing elit。 Pellentesque id congue massa。 Curabitur ##test3|id=5## egestas ullamcorper sollicitudin。 Mauris venenatis sed metus vitae pharetra。

到目前为止,我有两个选择。从优化的角度考虑哪个最好?

选项 1

~##(!?)(test|test2|test3)(|\S+?)##~s

选项 2

~\##(\S+)##~s

对于示例\##!test2## 中的"!",它旨在标记项目在处理时的特殊行为。这可以移动到像##test3|force=true&id=5## 这样的属性。如果是这样的话,那就是:

选项 3

~##(test|test2|test3)(|\S+?)##~s

我们关注的最大因素是性能和优化。

提前感谢您的帮助和洞察力!

【问题讨论】:

  • but how to benchmark and understand which is best? 运行它们并查看内存使用情况和运行代码的时间。
  • 我同意 Andreas,唯一的方法是进行大规模测试 (10000+) 并测量和比较您的结果
  • 前面的 cmets 是正确的,但是您遗漏了其他主要问题。您需要转义管道符号 (|),如 (\|?)。您不需要转义井号 (#)。此外,您的参数对于正则表达式应该匹配的内容并不完全清楚。但是对于您正在尝试做的事情,最简单可能最快的正则表达式可能看起来像这样:~##[^\s#]+?##~s
  • 尽可能避免交替,因为引擎必须进入每个分支才能找到令人满意的路径。最好的情况是通过第一面。更少的模式通常意味着更高的效率。根据需要应用修饰符。 s 会影响您甚至没有使用过的 .。尽可能贪婪。引擎喜欢它。 ~##[^#]*##~

标签: php regex optimization


【解决方案1】:

正如其他人所提到的,你需要为你的表达计时。 Python 具有出色的 timeit 模块,而对于 PHP,您需要提出自己的解决方案:

<?php

$string = <<<DATA
Lorem ipsum dolor sit amet, ##test## consectetur adipiscing elit. Pellentesque id congue massa. Curabitur ##test3|id=5## egestas ullamcorper sollicitudin. Mauris venenatis sed metus vitae pharetra.
DATA;

function timeit($regex, $string, $number) {
    $start = microtime(true);

    for($i=0;$i<$number;$i++) {
        preg_match_all($regex, $string, $matches);
    }

    return microtime(true) - $start;
}

$expressions = ['~##(!?)(test|test2|test3)(|\S+?)##~s', '~\##(\S+)##~s', '~##(test|test2|test3)(|\S+?)##~s'];
$cnt = 1;
foreach ($expressions as $expression) {
    echo "Expression " . $cnt . " took " . timeit($expression, $string, 10**5) . "\n";
    $cnt++;
}
?>


在我的计算机上运行它(每个 100k 迭代)产生
Expression 1 took 0.45759010314941
Expression 2 took 0.34269499778748
Expression 3 took 0.40994691848755

显然,您可以使用其他字符串和更多迭代,但这会给您一个大致的概念。

【讨论】:

  • 谢谢!这个基准测试脚本非常非常有用。
  • @MrC:如果对您有帮助,您可以投票/接受它作为答案(左侧的绿色勾号)。
【解决方案2】:

如果您需要根据字符出现来剖析和处理匹配的子字符串,在正则表达式步骤中分离组件似乎是最合乎逻辑的 - 在消除准确性和易于处理之后,请关注模式优化。

我的模式包含三个捕获组,只有中间一个需要正长度字符串。取反的捕获组用于模式效率。我假设您的子字符串不会包含用于分隔子字符串的#。如果它们可能包含#,那么请更新您的问题,我会更新我的答案。

Pattern Demo

模式说明:

/          // pattern delimiter
##         // match leading substring delimiter
(!)?       // optionally capture: an exclamation mark
([^#|]+)   // greedily capture: one or more non-hash, non-pipe characters
\|?        // optionally match: a pipe
([^#]+)?   // optionally capture: one or more non-hash characters
##         // match trailing substring delimiter
/          // pattern delimiter

代码:(Demo)

$string='Lorem ipsum dolor sit amet, ##test## consectetur adipiscing elit. Pellentesque id congue massa. Curabitur ##test3|id=5## egestas ullamcorper sollicitudin. Mauris venenatis sed metus ##!test2## vitae pharetra.';

$result=preg_replace_callback(
    '/##(!)?([^#|]+)\|?([^#]+)?##/',
    function($m){
        echo '$m = ';
        var_export($m);
        echo "\n";
        // execute custom processing:
        if(isset($m[1][0])){  //check first character of element (element will always be set because $m[2] will always be set)
            echo "exclamation found\n";
        }
        // $m[2] is required (will always be set)
        if(isset($m[3])){  // will only be set if there is a positive-length string in it
            echo "post-pipe substring found\n";
        }
        echo "\n---\n";
        return '[some replacement text]';
    },$string);

var_export($result);

输出:

$m = array (
  0 => '##test##',
  1 => '',
  2 => 'test',
)

---
$m = array (
  0 => '##test3|id=5##',
  1 => '',
  2 => 'test3',
  3 => 'id=5',
)
post-pipe substring found

---
$m = array (
  0 => '##!test2##',
  1 => '!',
  2 => 'test2',
)
exclamation found

---
'Lorem ipsum dolor sit amet, [some replacement text] consectetur adipiscing elit. Pellentesque id congue massa. Curabitur [some replacement text] egestas ullamcorper sollicitudin. Mauris venenatis sed metus [some replacement text] vitae pharetra.'

如果您正在执行自定义替换过程,此方法将“优化”您的字符串处理。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多