Pergunta

Este é apenas um projeto pessoal que eu estive cavando. Basicamente, eu analisar um arquivo de texto (digamos de 20mb até cerca de 1GB) usando StreamReader. O desempenho é bastante sólido, mas ainda assim ... Eu tenho sido comichão para ver o que aconteceria se eu analisá-lo em binário. Não entenda mal, eu não estou otimizar prematuramente. Estou definitivamente micro-otimização de propósito só "para ver".

Então, eu estou lendo no arquivo de texto usando matrizes de bytes. Venha descobrir, novas linhas podem ser o (Windows) padrão CR / LF ou CR ou LF ... bastante confuso. Eu tinha a esperança de ser capaz de usar Array.indexOf no CR e depois pular o LF. Em vez disso eu encontro-me escrever código muito semelhante ao IndexOf mas a verificação de qualquer um e retornando uma matriz conforme necessário.

Assim, o ponto crucial: usando o código muito semelhante ao IndexOf, meu código ainda acaba sendo insanamente mais lento. Para colocar isso em perspectiva usando um arquivo de 800MB:

  • Usando IndexOf e procurando CR: ~ 320MB / s
  • Usando StreamReader e ReadLine: ~ 180MB / s
  • loop for replicante IndexOf: ~ 150MB / s

aqui está o código com o loop for (~ 150MB / s):

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

Este é o bloco de código mais rápido (~ 320MB / s):

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

(Não, não é produção pronta, certos casos irá torná-lo explodir;. Eu uso um tamanho de 128kb tampão de ignorar a maioria das pessoas)

Assim, a minha grande questão é ... por que faz o trabalho Array.indexOf muito mais rápido? É essencialmente a mesma, para um circuito de caminhar uma matriz. Existe algo sobre o código de maneira mscorlib é executado? Mesmo mudando o código acima para IndexOf realmente replicar e procurando apenas CR e depois pular LF como eu faria se usando IndexOf não ajuda. Errr ... Eu tenho passado por várias permutações e é bastante tarde que talvez haja algum erro gritante eu estou ausente?

BTW, eu olhei para ReadLine e notei que usa um bloco de interruptor em vez de um bloco if ... quando eu fazer algo semelhante, estranhamente o suficiente ele faz aumentar o desempenho em cerca de 15MB / s. Essa é outra pergunta para um outro tempo (por isso é mudar mais rapidamente do que se?), Mas eu percebi que eu salientar que eu fiz olhar para ele.

Além disso, estou testando um fora liberação construção de VS para que não haja debuggery acontecendo.

Foi útil?

Solução

Essa é uma boa pergunta. A versão curta é que tudo se resume à implementação do IEqualityComparer que IndexOf vai usar. Vamos ver o seguinte trecho de código:

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

Você precisará compilá-lo com csc / optimize + .

Aqui está o resultado que eu tenho:

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

Agora, mude o tipo da matriz e do EqualityComparer a byte, e aqui está o resultado que eu tenho:

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

Como você pode ver, a matriz de bytes é revestido especial, o que provavelmente é otimizado para encontrar um byte em um array de bytes. Como eu não posso descompilar o .NET framework, parei a analisar aqui, mas eu acho que é um bom indício bonita.

Outras dicas

Os arquivos mscorlib são ngen'd durante a instalação. Tente ngen'ing seu arquivo usando o utilitário Ngen.exe (fornecido juntamente com .NET framwork suponho) ... e depois verificar os benchmarks. Poderia ser um pouco mais rápido.

Para fazer o seu código .NET correr a velocidades quase nativas, a Microsoft recomenda que você "Ngen" seu código durante a instalação do aplicativo ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top