Как я могу оптимизировать этот анализатор UserAgent для цикла в C #? - PullRequest
2 голосов
/ 13 сентября 2011

Я пишу программу на C # для анализа количества браузеров в столбце 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 Ответ

3 голосов
/ 13 сентября 2011

Вы можете

  • создать хеш-таблицу или наиболее часто совпадающий пользовательский агент и избежать совпадения с регулярным выражением.

  • хранить скомпилировать новый Regex(pattern, RegexOptions.Compiled) вместо просто pattern

  • объединить регулярные выражения в одном регулярном выражении и использовать преимущества RegexOptions.Compiledи RegexOptions.CultureInvariantIgnoreCase

  • вместо сопоставления дважды (один раз с IsMatch и один раз с Matches) сопоставляют один раз (Matches) и проверяют, является ли MatchCollection пустой

Это только отправная точка - я мог бы предложить больше идей по прочтению кода:)

Редактировать Еще один:

  • Избегайте синтаксического анализа версии с другим регулярным выражением - только safari требует специальных обработок в соответствии с вашей конфигурацией.Попробуйте «поймать» версию с тем же регулярным выражением, что и у browserid.(Я бы сейчас сделал исключение для сафари)

Например, у вас может быть один экземпляр статического регулярного выражения, например:

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. замена отдельных регулярных выражений одним статически скомпилированным регулярным выражением, ускоряется с 14 с до 2,1 с ( 6-кратное ускорение ); это только с заменой самого внешнего совпадения

  2. замена regex_not / regex_version на предварительно скомпилированные регулярные выражения не имела большого значения для моего набора тестов (но я нефактические совпадающие значения используются, поэтому это имеет смысл)

.

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