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

StackOverflow https://stackoverflow.com/questions/891835

Вопрос

Это всего лишь личный проект, над которым я копался.По сути, я анализирую текстовый файл (скажем, от 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 работает намного быстрее?По сути, это то же самое: цикл for, проходящий по массиву.Есть ли что-то в способе выполнения кода mscorlib?Даже изменение приведенного выше кода для действительной репликации IndexOf и поиск только CR, а затем пропуск LF, как я бы сделал, если бы использование IndexOf не помогло.Эээ...Я перепробовал различные варианты, и уже достаточно поздно, и, возможно, мне не хватает какой-то явной ошибки?

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

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

Это было полезно?

Решение

Это хороший вопрос.Вкратце, все сводится к реализации 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 /оптимизировать+.

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

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

Теперь измените тип массива и EqualityComparer на byte, и вот результат:

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

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

Другие советы

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

Чтобы ваш .NET-код работал на скорости, близкой к исходной, Microsoft рекомендует вам «Ngen» вашего кода во время установки приложения...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top