Pregunta

Esto es sólo un proyecto personal que he estado cavando en. Básicamente, analizar un archivo de texto (digamos de 20 MB hasta 1 GB) utilizando StreamReader. El rendimiento es bastante sólido, pero aún así ... he estado con ganas de ver lo que sucedería si analizarlo en binario. No malinterpreten, no estoy prematuramente optimización. Estoy definitivamente micro-optimización a propósito sólo "ver".

Por lo tanto, estoy leyendo en el archivo de texto usando matrices de bytes. Volver a encontrar, nuevas líneas pueden ser el (Windows) estándar CR / LF o CR o LF ... bastante desordenado. Tenía la esperanza de poder utilizar Array.indexOf en CR y luego saltar más allá de la LF. En su lugar me encuentro escribiendo código muy similar a IndexOf pero la comprobación de cualquiera y devuelve una matriz según sea necesario.

Así que el punto crucial: el uso de un código muy similar a IndexOf, mi código todavía termina siendo increíblemente lento. Para ponerlo en perspectiva con un archivo de 800 MB:

  • Uso IndexOf y en busca de CR: ~ 320 MB / s
  • Uso StreamReader y ReadLine: ~ 180 MB / s
  • bucle for IndexOf replicar: ~ 150 MB / s

Este es el código con el bucle (~ 150 MB / 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 es el bloque de código más rápido (~ 320 MB / 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);
    }
}

(No, no es de producción listo, ciertos casos lo harán estallar;. Yo uso un búfer de tamaño 128kb hacer caso omiso de la mayoría de las personas)

Así que mi gran pregunta es ... ¿Por qué funciona Array.indexOf mucho más rápido? Se trata esencialmente de la misma, un bucle que recorre una matriz. ¿Hay algo en el código de manera mscorlib se ejecuta? Incluso cambiar el código anterior para replicar realmente IndexOf y buscando simplemente CR y luego saltar LF igual que lo haría si se utiliza IndexOf no ayuda. Errr ... He estado yendo a través de diversas permutaciones y es lo suficientemente tarde que tal vez hay algún fallo evidente que me falta?

Por cierto, me miró a los ReadLine y notamos que utiliza un bloque de interruptores en lugar de un bloque if ... cuando hago algo similar, extrañamente suficiente que hace aumentar el rendimiento en alrededor de 15 MB / s. Esa es otra pregunta para otro momento (por eso es cambiar más rápido que si?), Pero pensé que había que señalar que yo veía en él.

Además, estoy probando un comunicado de construir fuera de VS lo que no hay debuggery pasando.

¿Fue útil?

Solución

Esa es una buena pregunta. La versión corta es que todo se reduce a la aplicación de la IEqualityComparer que IndexOf utilizará. Veamos el siguiente fragmento 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);
    }
}

Usted tendrá que compilarlo con csc / Optimizar + .

Aquí está el resultado que tengo:

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

Ahora, cambiar el tipo de la matriz y de la EqualityComparer a byte, y aquí está el resultado que tengo:

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

Como se puede ver, la matriz de bytes se revistió especial, que probablemente está optimizado para encontrar un byte en una matriz de bytes. Como no puedo descompilar el marco .NET, dejé el analizar aquí, pero supongo que es una buena idea.

Otros consejos

Los archivos se mscorlib ngen'd durante la instalación. Trate ngen'ing su archivo usando la utilidad Ngen.exe (proporcionado junto con framwork .NET supongo) ... y luego comprobar los puntos de referencia. Podría ser un poco más rápido.

Para hacer que el código .NET correr a una velocidad cercana a la nativa, Microsoft recomienda que "Ngen" su código durante la instalación de aplicaciones ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top