代码之家  ›  专栏  ›  技术社区  ›  Boldewyn

PHP中标记化CSS的性能

  •  4
  • Boldewyn  · 技术社区  · 16 年前

    这是以前从未写过解析器/lexer的人提出的一个无意义的问题。

    我正在用PHP编写一个用于CSS的标记器/解析器(请不要用“omg,why in php?”重复)。语法由W3C整齐地写下。 here (CSS2.1) 和 here (CSS3, draft) .

    它列出了21个可能的令牌,所有(除了两个)都不能表示为静态字符串。

    我目前的方法是循环遍历一个包含21个模式的数组,执行一次又一次的 if (preg_match()) 并按匹配减少源字符串匹配。原则上,这真的很管用。但是,对于一个1000行的CSS字符串,这需要2到8秒的时间,这对于我的项目来说太多了。

    现在我在敲我的头,其他解析器是如何标记的。 和 在几秒钟内解析CSS。好的,C 是 总是比PHP快,但是,有没有明显的 哦! 是我掉进的吗?

    我做了一些优化,比如检查剩余字符串的第一个字符是“@”、“”或“”,然后只应用相关的regexp,但这并没有带来任何显著的性能提升。

    迄今为止我的代码(代码片段):

    $TOKENS = array(
      'IDENT' => '...regexp...',
      'ATKEYWORD' => '@...regexp...',
      'String' => '"...regexp..."|\'...regexp...\'',
      //...
    );
    
    $string = '...CSS source string...';
    $stream = array();
    
    // we reduce $string token by token
    while ($string != '') {
        $string = ltrim($string, " \t\r\n\f"); // unconsumed whitespace at the
            // start is insignificant but doing a trim reduces exec time by 25%
        $matches = array();
        // loop through all possible tokens
        foreach ($TOKENS as $t => $p) {
            // The '&' is used as delimiter, because it isn't used anywhere in
            // the token regexps
            if (preg_match('&^'.$p.'&Su', $string, $matches)) {
                $stream[] = array($t, $matches[0]);
                $string = substr($string, strlen($matches[0]));
                // Yay! We found one that matches!
                continue 2;
            }
        }
        // if we come here, we have a syntax error and handle it somehow
    }
    
    // result: an array $stream consisting of arrays with
    // 0 => type of token
    // 1 => token content
    
    5 回复  |  直到 13 年前
        1
  •  3
  •   erikkallen    16 年前
        2
  •  0
  •   zombat    16 年前

    我要做的第一件事就是把 preg_match() . 基本字符串函数,如 strpos() 速度快得多,但我觉得你根本不需要。看起来您正在查找字符串前面的特定标记, 预匹配() ,然后简单地将该字符串的前面长度作为子字符串。你可以用一个简单的 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() 总是。如果你排序 美元代币 按长度分组,可以减少 子() 电话也打得更远,因为你可以 substr($string) 对于每个令牌长度,只分析一次当前字符串,并在转到下一组令牌之前运行该长度的所有令牌。

        3
  •  0
  •   user187291    16 年前

    (可能)更快(但对内存不太友好)的方法是立即标记整个流,使用一个大的regexp和每个标记的替代项,例如

     preg_match_all('/
           (...string...)
           |
           (@ident)
           |
           (#ident)
           ...etc
       /x', $stream, $tokens);
    
     foreach($tokens as $token)...parse
    
        4
  •  0
  •   Ming-Tang    16 年前

    不要使用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 (......) {
        // ......
      }
    }
    

    然而,这使得代码不可维护,因此解析器生成器更好。

        5
  •  0
  •   Nir    13 年前

    这是一个旧的帖子,但我还是贡献了我的2美分。 有一点会严重减慢问题中原始代码的速度,那就是下面这一行:

    $string = substr($string, strlen($matches[0]));
    

    而不是处理整个字符串,只取其中的一部分(比如50个字符),这足以满足所有可能的正则表达式。然后,在上面应用相同的代码行。当这个字符串收缩到预设长度以下时,再加载一些数据。