2011-09-13 30 views
2

我在写一个C#程序来分析Web服务器日志的UserAgent列中的浏览器数量。我希望输出浏览器类型,浏览器主要版本和点击次数。如何优化此UserAgent解析器for C#中的循环?

我该如何优化?

我正在使用正则表达式来比较UserAgent字符串与预定义的字符串来测试Firefox,Opera等,然后使用正则表达式来抵消可能的不匹配。然后我使用正则表达式来获取主要版本。我用一个结构来保存这些信息对每个浏览:

private struct Browser 
{ 
    public int ID; 
    public string name; 
    public string regex_match; 
    public string regex_not; 
    public string regex_version; 
    public int regex_group; 
} 

我然后加载了所有记录的用户代理浏览器的信息和循环:

Browser[] browsers = new Browser[5]; 
for (int i = 0; i < 5; i++) 
{ 
    browsers[i].ID = i; 
} 
browsers[0].name = "Firefox"; 
browsers[1].name = "Opera"; 
browsers[2].name = "Chrome"; 
browsers[3].name = "Safari"; 
browsers[4].name = "Internet Explorer"; 
browsers[0].regex_match = "(?i)firefox/([\\d\\.]*)"; 
browsers[1].regex_match = "(?i)opera/([\\d\\.]*)"; 
browsers[2].regex_match = "(?i)chrome/([\\d\\.]*)"; 
browsers[3].regex_match = "(?i)safari/([\\d\\.]*)"; 
browsers[4].regex_match = "(?i)msie([+_ ]|)([\\d\\.]*)"; 
browsers[0].regex_not = "(?i)flock"; 
browsers[1].regex_not = ""; 
browsers[2].regex_not = ""; 
browsers[3].regex_not = "(?i)android|arora|chrome|shiira"; 
browsers[4].regex_not = "(?i)webtv|omniweb|opera"; 
browsers[0].regex_version = "(?i)firefox/([\\d\\.]*)"; 
browsers[1].regex_version = "(?i)opera/([\\d\\.]*)"; 
browsers[2].regex_version = "(?i)chrome/([\\d\\.]*)"; 
browsers[3].regex_version = "(?i)version/([\\d\\.]*)"; 
browsers[4].regex_version = "(?i)msie([+_ ]|)([\\d\\.]*)"; 
browsers[0].regex_group = 1; 
browsers[1].regex_group = 1; 
browsers[2].regex_group = 1; 
browsers[3].regex_group = 1; 
browsers[4].regex_group = 2; 
Dictionary<string, int> browser_counts = new Dictionary<string, int>(); 
for (int i = 0; i < 65000; i++) 
{ 
    foreach (Browser b in browsers) 
    { 
     if (Regex.IsMatch(csUserAgent[i], b.regex_match)) 
     { 
      if (b.regex_not != "") 
      { 
       if (Regex.IsMatch(csUserAgent[i], b.regex_not)) 
       { 
        continue; 
       } 
      } 
      string strBrowser = b.name; 
      if (b.regex_version != "") 
      { 
       string strVersion = Regex.Match(csUserAgent[i], b.regex_version).Groups[b.regex_group].Value; 
       int intPeriod = strVersion.IndexOf('.'); 
       if (intPeriod > 0) 
       { 
        strBrowser += " " + strVersion.Substring(0, intPeriod); 
       } 
      } 
      if (!browser_counts.ContainsKey(strBrowser)) 
      { 
       browser_counts.Add(strBrowser, 1); 
      } 
      else 
      { 
       browser_counts[strBrowser]++; 
      } 
      break; 
     } 
    } 
} 

回答

3

你可以

  • 构造一个散列表或最常见的匹配用户代理并避免匹配正则表达式。

  • 店编译的新Regex(pattern, RegexOptions.Compiled)而不是仅仅pattern

  • 的正则表达式组合成一个单一的正则表达式,并采取RegexOptions.Compiled的优势,并与IsMatch RegexOptions.CultureInvariantIgnoreCase

  • ,而不是匹配两次(一次一次与Matches)匹配一次(Matches)并检查MatchCollection是否为空

这仅仅是一个起点 - 我可能会拿出更多的想法在阅读代码:)

编辑还有一个:

  • 避免解析与​​另一个正则表达式的版本 - 唯一的野生动物园需要特殊treaetment根据你的配置。尝试使用与browserid相同的正则表达式“捕捉”版本。 (我只是破例为Safari浏览器现在)

例如你可以有一个单一的静态正则表达式的实例是这样的:

private static readonly Regex _regex = new Regex(
    "(?i)" 
    + "(?<browserid>(?:firefox/|opera/|chrome/|chrome/|safari/|msie[+_ ]?))" 
    + "(?<version>[\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 

您可以方便地使用match.Groups["browserid"]match.Groups["version"]访问正确的分组。这几乎消除了浏览器结构列表的所有用途。

它仍然唯一迎合的是排除正则表达式(regex_not)。我建议首先重新分析一个正面的正则表达式,然后看看在炸小鱼之前是否还有性能问题。

Benchmark

我写了一个基准(见下文)。我会逐步更新,直到我失去兴趣:)(我知道我的数据集不具代表性。如果您上传文件,我也有同感

  1. 由单一的静态编译的正则表达式替换单独的正则表达式进行测试,从14秒到2.1s时(6倍加速)加快; 这仅仅是从最外端的匹配替换

  2. 更换由预编译的正则表达式的regex_not/regex_version并没有多大的差别与我的测试集(但我没有实际的匹配useragents,这样才有意义)

using System; 
using System.Linq; 
using System.Collections.Generic; 
using System.Text.RegularExpressions; 


public class Program 
{ 
    private struct Browser 
    { 
     public int ID; 
     public string name; 
     public Regex regex_match, regex_not, regex_version; 
     public int regex_group; 
    } 

    private static readonly Regex _regex = new Regex("(?i)" 
     + "(?<browserid>(?:firefox/|opera/|chrome/|chrome/|safari/|msie[+_ ]?))" 
     + "(?<version>[\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 

    public static void Main(string[] args) 
    { 

     Browser[] browsers = new Browser[5]; 
     for (int i = 0; i < 5; i++) 
     { 
      browsers[i].ID = i; 
     } 
     browsers[0].name = "Firefox"; 
     browsers[1].name = "Opera"; 
     browsers[2].name = "Chrome"; 
     browsers[3].name = "Safari"; 
     browsers[4].name = "Internet Explorer"; 
     browsers[0].regex_match = new Regex("(?i)firefox/([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[1].regex_match = new Regex("(?i)opera/([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[2].regex_match = new Regex("(?i)chrome/([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[3].regex_match = new Regex("(?i)safari/([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[4].regex_match = new Regex("(?i)msie([+_ ]|)([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     // OPTIMIZATION #2 
     browsers[0].regex_not = new Regex("(?i)flock", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[1].regex_not = null; 
     browsers[2].regex_not = null; 
     browsers[3].regex_not = new Regex("(?i)android|arora|chrome|shiira", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[4].regex_not = new Regex("(?i)webtv|omniweb|opera", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     // OPTIMIZATION #2 
     browsers[0].regex_version = new Regex("(?i)firefox/([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[1].regex_version = new Regex("(?i)opera/([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[2].regex_version = new Regex("(?i)chrome/([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[3].regex_version = new Regex("(?i)version/([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[4].regex_version = new Regex("(?i)msie([+_ ]|)([\\d\\.]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant); 
     browsers[0].regex_group = 1; 
     browsers[1].regex_group = 1; 
     browsers[2].regex_group = 1; 
     browsers[3].regex_group = 1; 
     browsers[4].regex_group = 2; 
     Dictionary<string, int> browser_counts = new Dictionary<string, int>(); 

     var lookupBrowserId = new Dictionary<string, int> { 
      { "firefox/", 0 }, 
      { "opera/", 1 }, 
      { "chrome/", 2 }, 
      { "safari/", 3 }, 
      { "msie+", 4 }, 
      { "msie_", 4 }, 
      { "msie ", 4 }, 
      { "msie", 4 }, 
     }; 

     for (int i=1; i<20; i++) 
     foreach (var line in System.IO.File.ReadAllLines("/etc/dictionaries-common/words")) 
     { 
      // OPTIMIZATION #1 START 
      Match match = _regex.Match(line); 

      { 
       if (match.Success) 
       { 
        Browser b = browsers[lookupBrowserId[match.Groups["browserid"].Value]]; 
        // OPTIMIZATION #1 END 

        // OPTIMIZATION #2 
        if (b.regex_not != null && b.regex_not.IsMatch(line)) 
          continue; 

        string strBrowser = b.name; 
        if (b.regex_version != null) 
        { 
         // OPTIMIZATION #2 
         string strVersion = b.regex_version.Match(line).Groups[b.regex_group].Value; 
         int intPeriod = strVersion.IndexOf('.'); 
         if (intPeriod > 0) 
         { 
          strBrowser += " " + strVersion.Substring(0, intPeriod); 
         } 
        } 
        if (!browser_counts.ContainsKey(strBrowser)) 
        { 
         browser_counts.Add(strBrowser, 1); 
        } 
        else 
        { 
         browser_counts[strBrowser]++; 
        } 
        break; 
       } 
      } 
     } 
    } 
} 
+0

写了一个组合的预编译正则表达式;使其成为静态类成员。它应该快几个数量级(尤其是一旦你避免与IsMatch/Matches匹配两次) – sehe

+0

heh。我修正了显然在2011年9月13日发生的标记失败(除非从那时起降价实施发生变化...) – sehe