如何在C#中优化此UserAgent解析器for循环?

时间:2011-09-13 15:40:02

标签: string optimization c#-4.0 user-agent

我正在编写一个C#程序来分析Web服务器日志的UserAgent列中的浏览器数量。我希望输出浏览器类型,浏览器主要版本和点击次数。

如何优化此功能?

我正在使用正则表达式将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;
}

然后我加载浏览器信息并循环遍历UserAgent的所有记录:

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;
        }
    }
}

1 个答案:

答案 0 :(得分:3)

你可以

  • 构造哈希表或最频繁地匹配用户代理并避免匹配正则表达式。

  • 存储编译新Regex(pattern, RegexOptions.Compiled)而非pattern

  • 将正则表达式组合成单个正则表达式并利用RegexOptions.Compiled和RegexOptions.CultureInvariantIgnoreCase

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

这只是一个起点 - 我可能会在阅读代码时提出更多想法:)

编辑还有一个:

  • 避免使用另一个正则表达式解析版本 - 只有safari需要根据您的配置进行特殊处理。尝试使用与browserid相同的正则表达式“捕获”该版本。 (我现在只是为野生动物园制作一个例外)

E.g。你可以有一个像这样的静态正则表达式实例:

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)。我建议首先使用单正正则表达式进行重新分析,然后看看在煎炸小鱼之前是否还存在性能问题。

基准

我写了一个基准(见下文)。我会逐渐更新这个,直到我失去兴趣:)(我知道我的数据集不具代表性。如果你上传一个文件,我会用它来测试

  1. 用单个静态编译的正则表达式替换单独的正则表达式,从14s加速到2.1s( 6x加速); 仅在最外面的比赛被替换

  2. 用预编译的正则表达式替换regex_not / regex_version并没有对我的测试集产生太大影响(但我没有实际匹配的用户,所以这很有意义)

  3. 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;
                    }
                }
            }
        }
    }