Почему код System / mscorlib намного быстрее? Особенно для петель? - PullRequest
4 голосов
/ 21 мая 2009

Это просто личный проект, в который я копался. По сути, я анализирую текстовый файл (скажем, от 20 МБ до 1 ГБ) с помощью StreamReader. Производительность довольно хорошая, но все же ... Мне не терпелось увидеть, что произойдет, если я проанализирую его в двоичном формате. Не поймите меня неправильно, я не оптимизирую преждевременно. Я определенно микрооптимизируюсь нарочно, просто чтобы «увидеть».

Итак, я читаю в текстовом файле, используя байтовые массивы. Приходите, чтобы узнать, что новые строки могут быть (Windows) стандартными CR / LF или CR или LF ... довольно грязно. Я надеялся, что смогу использовать Array.IndexOf на CR, а затем пропустить LF. Вместо этого я пишу код, очень похожий на IndexOf, но проверяю любой из них и возвращаю массив по мере необходимости.

Итак, суть: используя код, очень похожий на IndexOf, мой код все равно оказывается безумно медленным. Чтобы поместить это в перспективу, используя файл 800 МБ:

  • Использование IndexOf и поиск CR: ~ 320 МБ / с
  • Использование StreamReader и ReadLine: ~ 180 МБ / с
  • для репликации цикла IndexOf: ~ 150 МБ / с

вот код с циклом for (~ 150 Мбит / с):

IEnumerator<byte[]> IEnumerable<byte[]>.GetEnumerator() {
    using(FileStream fs = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, _bufferSize)) {
        byte[] buffer = new byte[_bufferSize];
        int bytesRead;
        int overflowCount = 0;
        while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
            int bufferLength = bytesRead + overflowCount;
            int lastPos = 0;
            for(int i = 0; i < bufferLength; i++) {
                if(buffer[i] == 13 || buffer[i] == 10) {
                    int length = i - lastPos;
                    if(length > 0) {
                        byte[] line = new byte[length];
                        Array.Copy(buffer, lastPos, line, 0, length);
                        yield return line;
                    }
                    lastPos = i + 1;
                }
            }
            if(lastPos > 0) {
                overflowCount = bufferLength - lastPos;
                Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
            }
        }
    }
}

это более быстрый кодовый блок (~ 320 МБ / с):

while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
    int bufferLength = bytesRead + overflowCount;
    int pos = 0;
    int lastPos = 0;
    while(pos < bufferLength && (pos = Array.IndexOf<byte>(buffer, 13, pos)) != -1) {
        int length = pos - lastPos;
        if(length > 0) {
            byte[] line = new byte[length];
            Array.Copy(buffer, lastPos, line, 0, length);
            yield return line;
        }
        if(pos < bufferLength - 1 && buffer[pos + 1] == 10)
            pos++;
        lastPos = ++pos;

    }
    if(lastPos > 0) {
        overflowCount = bufferLength - lastPos;
        Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
    }
}

(Нет, он не готов к работе, в некоторых случаях он будет взорван; я использую буфер размером 128 КБ, чтобы игнорировать большинство из них.)

Итак, мой большой вопрос ... почему Array.IndexOf работает намного быстрее? Это по сути то же самое, для цикла обход массива. Есть ли что-то в способе выполнения кода mscorlib? Даже изменение приведенного выше кода для реальной репликации IndexOf и поиск только CR, а затем пропуск LF, как если бы я использовал IndexOf, не помогает. Э-э-э ... Я проходил различные перестановки, и уже достаточно поздно, чтобы, возможно, я обнаружил какую-то явную ошибку?

Кстати, я заглянул в ReadLine и заметил, что он использует блок переключателей, а не блок if ... когда я делаю что-то подобное, как ни странно, это увеличивает производительность примерно на 15 Мбит / с. Это еще один вопрос для другого времени (почему переключение происходит быстрее, чем если бы?), Но я решил, что укажу, что я действительно посмотрел на него.

Кроме того, я тестирую сборку релиза вне VS, поэтому отладки не происходит.

Ответы [ 2 ]

2 голосов
/ 21 мая 2009

Хороший вопрос. Короткая версия состоит в том, что все сводится к реализации IEqualityComparer, который будет использовать IndexOf. Давайте посмотрим на следующий фрагмент кода:

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Program {

    static int [] buffer = new int [1024];
    const byte mark = 42;
    const int iterations = 10000;

    static void Main ()
    {
        buffer [buffer.Length -1] = mark;

        Console.WriteLine (EqualityComparer<int>.Default.GetType ());

        Console.WriteLine ("Custom:  {0}", Time (CustomIndexOf));
        Console.WriteLine ("Builtin: {0}", Time (ArrayIndexOf));
    }

    static TimeSpan Time (Action action)
    {
        var watch = new Stopwatch ();
        watch.Start ();
        for (int i = 0; i < iterations; i++)
            action ();
        watch.Stop ();
        return watch.Elapsed;
    }

    static void CustomIndexOf ()
    {
        for (int i = 0; i < buffer.Length; i++)
            if (buffer [i] == mark)
                break;
    }

    static void ArrayIndexOf ()
    {
        Array.IndexOf (buffer, mark);
    }
}

Вам нужно будет скомпилировать его с csc /optimize+.

Вот результат, который у меня есть:

C:\Tmp>test
System.Collections.Generic.GenericEqualityComparer`1[System.Int32]
Custom:  00:00:00.0386403
Builtin: 00:00:00.0427903

Теперь измените тип массива и EqualityComparer на байты, и вот результат, который у меня есть:

C:\Tmp>test
System.Collections.Generic.ByteEqualityComparer
Custom:  00:00:00.0387158
Builtin: 00:00:00.0165881

Как вы можете видеть, байтовый массив имеет специальный регистр, который, вероятно, оптимизирован для поиска байта в байтовом массиве. Поскольку я не могу декомпилировать .net framework, я остановил анализ здесь, но я думаю, что это довольно хорошая подсказка.

2 голосов
/ 21 мая 2009

Файлы mscorlib ngen'd во время установки. Попробуйте создать файл с помощью утилиты Ngen.exe (я полагаю, вместе с .NET framwork) ... и затем проверьте тесты. Может быть немного быстрее.

Чтобы ваш код .NET работал на почти родной скорости, Microsoft рекомендует вам "Ngen" код во время установки приложения ...

...