PHP中标记化CSS的性能
这是一个从未编写过解析器/词法分析器的人提出的noob问题 我正在用PHP为CSS编写一个标记器/解析器(请不要重复“OMG,为什么用PHP?”)。语法由W3C简洁明了地写下来 这是一个包含21个可能标记的列表,所有标记(除了两个)都不能表示为静态字符串 我目前的方法是反复遍历包含21个模式的数组,执行PHP中标记化CSS的性能,php,performance,parsing,token,lexer,Php,Performance,Parsing,Token,Lexer,这是一个从未编写过解析器/词法分析器的人提出的noob问题 我正在用PHP为CSS编写一个标记器/解析器(请不要重复“OMG,为什么用PHP?”)。语法由W3C简洁明了地写下来 这是一个包含21个可能标记的列表,所有标记(除了两个)都不能表示为静态字符串 我目前的方法是反复遍历包含21个模式的数组,执行if(preg_match())并逐个减少源字符串匹配。从原则上讲,这真的很有效。然而,对于1000行CSS字符串,这需要2到8秒的时间,这对我的项目来说太多了 现在我在想其他解析器如何在几秒钟内
if(preg_match())
并逐个减少源字符串匹配。从原则上讲,这真的很有效。然而,对于1000行CSS字符串,这需要2到8秒的时间,这对我的项目来说太多了
现在我在想其他解析器如何在几秒钟内标记和解析CSS。好的,C总是比PHP快,但不管怎样,有没有明显的D'Oh!我掉进了什么地方
我做了一些优化,比如检查'@'、'#'或'''作为剩余字符串的第一个字符,然后只应用相关的regexp,但这并没有带来任何性能提升
到目前为止,我的代码(片段):
我要做的第一件事是去掉
preg\u match()
。基本的字符串函数,如strpos()
要快得多,但我认为您甚至不需要它。看起来您正在使用preg\u match()在字符串前面寻找特定的标记
,然后只需将该字符串的前端长度作为子字符串。您可以使用简单的substr()
来轻松完成此操作,如下所示:
foreach ($TOKENS as $t => $p)
{
$front = substr($string,0,strlen($p));
$len = strlen($p); //this could be pre-stored in $TOKENS
if ($front == $p) {
$stream[] = array($t, $string);
$string = substr($string, $len);
// Yay! We found one that matches!
continue 2;
}
}
通过预先计算所有令牌的长度并将其存储在$tokens
数组中,您可以进一步优化此功能,这样您就不必一直调用strlen()
。如果您将$tokens
按长度分组,则可以减少substr()的数量
还可以进一步调用,因为您可以对每个令牌长度只分析一次当前字符串的substr($string)
,并在转到下一组令牌之前遍历该长度的所有令牌。使用a。可能更快(但内存不太友好)方法是一次对整个流进行标记化,使用一个大的regexp和每个标记的替代项,如
preg_match_all('/
(...string...)
|
(@ident)
|
(#ident)
...etc
/x', $stream, $tokens);
foreach($tokens as $token)...parse
不要使用regexp,逐个字符扫描
$tokens = array();
$string = "...code...";
$length = strlen($string);
$i = 0;
while ($i < $length) {
$buf = '';
$char = $string[$i];
if ($char <= ord('Z') && $char >= ord('A') || $char >= ord('a') && $char <= ord('z') || $char == ord('_') || $char == ord('-')) {
while ($char <= ord('Z') && $char >= ord('A') || $char >= ord('a') && $char <= ord('z') || $char == ord('_') || $char == ord('-')) {
// identifier
$buf .= $char;
$char = $string[$i]; $i ++;
}
$tokens[] = array('IDENT', $buf);
} else if (......) {
// ......
}
}
$tokens=array();
$string=“…代码…”;
$length=strlen($string);
$i=0;
而($i<$length){
$buf='';
$char=$string[$i];
如果($char=ord('A')| |$char>=ord('A')&&$char=ord('A')&&$char,这是一篇旧文章,但我仍然为此贡献了2美分。
在问题中,有一件事会严重减慢原始代码的速度,那就是下面这行代码:
$string = substr($string, strlen($matches[0]));
与其处理整个字符串,不如只处理其中的一部分(比如50个字符),这对于所有可能的正则表达式来说都足够了。然后,在其上应用相同的代码行。当该字符串收缩到预设长度以下时,向其加载更多数据。Profile。使用XDebug生成分析数据并将其加载到KCacheGrind中。避免运行substr()如果可能的话,在源字符串上一遍又一遍地重新分配字符串并不是免费的。并且找到一些方法来减少您计算的正则表达式的数量。或者,更好的是,停止使用正则表达式。显而易见的D'oh!不是阅读lexer如何真正工作。关键思想是它们组成一组模式匹配(你称它们为regexp)到一个匹配器中,就像它一次应用所有匹配器一样“您的一次一个尝试模式的方案在性能上非常接近。问题是,我事先不知道令牌长度。例如,@-令牌可能是'@charset'、'@namespace'或'@import',但也可能是'@-moz document'之类的任意内容。它被定义为'@'后跟一个或多个[a-zA-Z0-9_-]或转义序列。”(如\10FFFF
)或者任何非ASCII Unicode字符。我可以放弃preg_match
,只处理“@”后面的字符,但是如果它是非ASCII字符,或者允许ASCII或ASCII作为转义序列的一部分,那么我必须逐字符进行测试,我认为这就是正则表达式引擎优化的目的。原则上,这是可行的(只要内存限制不被破坏)。但是,之后我必须循环遍历每个匹配,并找出它是什么类型的令牌。它可能会工作(在许多情况下,只需查看第一个字符串就足够了),但我怀疑它是否快得多。感谢指针。我将查看它(尤其是生成的代码).照erik的建议去做。除非你了解lexer生成器为你提供了什么以及它是如何工作的,否则你不会理解它为什么能如此惊人地快速地对输入流进行lex。只有链接的答案并没有那么好。但在这些答案中,链接断开的答案是完全无用的。
$string = substr($string, strlen($matches[0]));