string.IndexOf производительности - PullRequest
6 голосов
/ 29 октября 2011

Этот простой фрагмент кода на c #, предназначенный для поиска блоков сценариев в HTML, занимает 0,5 секунды для запуска строки 74K-символов, содержащей только 9 блоков сценариев.Это бинарный релиз без отладки на 2.8 ГГц процессоре i7.Я сделал несколько прогонов, хотя этот код, чтобы убедиться, что JIT не мешает производительности.Это не.

Это профиль клиента VS2010 .NET 4.0.x64

Почему это так медленно?

                int[] _exclStart = new int[100];
                int[] _exclStop = new int[100];
                int _excl = 0;
                for (int f = input.IndexOf("<script", 0); f != -1; )
                {
                    _exclStart[_excl] = f;
                    f = input.IndexOf("</script", f + 8);
                    if (f == -1)
                    {
                        _exclStop[_excl] = input.Length;
                        break;
                    }
                    _exclStop[_excl] = f;
                    f = input.IndexOf("<script", f + 8);
                    ++_excl;
                }

Ответы [ 5 ]

17 голосов
/ 30 октября 2011

Я использовал источник на этой странице в качестве примера, затем дублировал содержимое 8 раз, в результате чего страница длиной около 334 312 байт.Использование StringComparision.Ordinal приводит к огромной разнице в производительности.

string newInput = string.Format("{0}{0}{0}{0}{0}{0}{0}{0}", input.Trim().ToLower());
//string newInput = input.Trim().ToLower();

System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
int[] _exclStart = new int[100];
int[] _exclStop = new int[100];
int _excl = 0;
for (int f = newInput.IndexOf("<script", 0, StringComparison.Ordinal); f != -1; )
{
    _exclStart[_excl] = f;
    f = newInput.IndexOf("</script", f + 8, StringComparison.Ordinal);
    if (f == -1)
    {
        _exclStop[_excl] = newInput.Length;
        break;
    }
    _exclStop[_excl] = f;
    f = newInput.IndexOf("<script", f + 8, StringComparison.Ordinal);
    ++_excl;
}
sw.Stop();
Console.WriteLine(sw.Elapsed.TotalMilliseconds);

при выполнении 5 раз дает почти одинаковый результат для каждого (время цикла существенно не изменилось, поэтому для этого простого кода JIT почти не тратит время наскомпилируйте его)

Вывод с использованием исходного кода (в Миллисекунды ):

10.2786
11.4671
11.1066
10.6537
10.0723

Вывод с использованием вышеуказанного кода (в Миллисекундах ):

0.3055
0.2953
0.2972
0.3112
0.3347

Обратите внимание, что результаты моего теста составляют около 0,010 секунд (исходный код) и 0,0003 секунд (для порядкового кода).Это означает, что у вас есть что-то не так, кроме этого кода напрямую.

Если, как вы говорите, использование StringComparison.Ordinal ничего не делает для вашей производительности, то это означает, что вы используете неправильные таймеры для измерения времени вашей работы, или у вас естьбольшие издержки при чтении значения input, например при повторном чтении из потока, который вы иначе не осознаете.

Протестировано в Windows 7 x64, работающей на i5 3GHz с использованием .NET 4 Client Profile.

Предложения:

  1. использование StringComparison.Ordinal
  2. Убедитесь, что вы используете System.Diagnostics.Stopwatch для измерения производительности
  3. Объявите локальную переменную для input вместо использования значений, внешних по отношению к функции (например: string newInput = input.Trim().ToLower();)

Еще раз подчеркиваю, я получаю 50 раз быстрее скорость для тестовых данных, которые, по-видимому,более чем в 4 раза больше по размеру, используя тот же код, который вы предоставляете.Это значит, что мой тест выполняется примерно в 1044 * 200 раз быстрее , чем у вас, и это совсем не то, чего можно было бы ожидать, если бы мы оба работали в одной среде, и только i5 (я) против i7 (вы).

4 голосов
/ 29 октября 2011

Перегрузка IndexOf, которую вы используете, чувствительна к культуре, что влияет на производительность. Вместо этого используйте:

input.IndexOf("<script", 0, StringComparison.Ordinal); 
3 голосов
/ 29 октября 2011

Я бы порекомендовал использовать для этого RegEx, он предлагает значительное улучшение производительности, потому что выражения компилируются только один раз.В то время как IndexOf по сути является циклом, который выполняется на основе символов, что, вероятно, означает, что в вашем основном цикле for есть 3 «цикла», конечно, IndexOf не будет таким же медленным, как обычный цикл, но все же, когда входной размер увеличиваетсявремя увеличивается.Regex имеет встроенные функции, которые будут возвращать количество и позиции вхождений каждого шаблона, который вы определяете.

Редактировать: это может пролить немного света на производительность IndexOf IndexOf Perf

2 голосов
/ 30 октября 2011

Я просто тестирую производительность IndexOf с .NET 4.0 в Windows 7

public void Test()
{
    var input = "Hello world, I'm ekk. This is test string";

    TestStringIndexOfPerformance(input, StringComparison.CurrentCulture);
    TestStringIndexOfPerformance(input, StringComparison.InvariantCulture);
    TestStringIndexOfPerformance(input, StringComparison.Ordinal);

    Console.ReadLine();
}

private static void TestStringIndexOfPerformance(string input, StringComparison stringComparison)
{
    var count = 0;
    var startTime = DateTime.UtcNow;
    TimeSpan result;

    for (var index = 0; index != 1000000; index++)
    {
        count = input.IndexOf("<script", 0, stringComparison);
    }

    result = DateTime.UtcNow.Subtract(startTime);

    Console.WriteLine("{0}: {1}", stringComparison, count);
    Console.WriteLine("Total time: {0}", result.TotalMilliseconds);
    Console.WriteLine("--------------------------------");
}

И результат:

CurrentCulture:
    Total time: 225.4008

InvariantCulture:
    Total time: 187.2003

Ordinal:
    Total time: 124.8003

Как видите, производительность Ordinal немного лучше.

1 голос
/ 30 октября 2011

Я не обсуждаю здесь код, который, вероятно, может быть написан с помощью Regex и т. Д. ... но для меня это медленно, потому что IndexOf () * внутри * for всегда повторно сканирует строку с начала (она всегда начинается с индекса 0), вместо этого попробуйте выполнить сканирование с последнего найденного вхождения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...