Domanda

Questa è solo un progetto personale che ho scavato in. Fondamentalmente, io analizzare un file di testo (ad esempio da 20 MB fino a circa 1 GB) utilizzando StreamReader. La performance è abbastanza solido, ma ancora ... ho voglia di vedere cosa sarebbe successo se avessi analizzarlo in binario. Non fraintendetemi, non sto prematuramente ottimizzazione. Sono sicuramente micro-ottimizzazione apposta solo "vedere".

Quindi, sto leggendo nel file di testo utilizzando array di byte. Venite a scoprire, nuove linee possono essere la (Windows) di serie CR / LF o CR o LF ... piuttosto disordinato. Speravo di poter utilizzare Array.indexOf sulla CR e quindi andare oltre la LF. Invece mi ritrovo a scrivere codice molto simile a IndexOf, ma il controllo di entrambi e restituisce un array come necessario.

Quindi, il punto cruciale: utilizzando il codice molto simile a IndexOf, il mio codice finisce con l'essere ancora follemente più lento. Per dirla in prospettiva utilizzando un file di 800MB:

  • Uso IndexOf e alla ricerca di CR: ~ 320MB / s
  • Uso StreamReader e ReadLine: ~ 180MB / s
  • ciclo for replicare IndexOf: ~ 150MB / s

Ecco il codice con il ciclo 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);
            }
        }
    }
}

Questo è il blocco di codice più veloce (~ 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);
    }
}

(No, non è pronto per la produzione, alcuni casi la faranno saltare in aria;. Io uso un buffer di dimensioni 128KB di ignorare la maggior parte di quelli)

Quindi la mia grande domanda è ... perché fa Array.indexOf funziona in modo molto più veloce? È essenzialmente la stessa, un ciclo for cammina un array. C'è qualcosa circa il codice modo mscorlib viene eseguito? Anche cambiando il codice qui sopra per replicare davvero IndexOf e cercando solo CR e poi saltare LF come farei se si utilizza IndexOf non aiuta. Errr ... Vado attraverso varie permutazioni ed è abbastanza tardi che forse c'è qualche bug lampante che mi manca?

A proposito, ho guardato in ReadLine e ho notato che utilizza un blocco switch piuttosto che un blocco if ... quando faccio qualcosa di simile, stranamente abbastanza lo fa aumentare le prestazioni di circa 15 MB / s. Questa è un'altra domanda per un altro tempo (perché è passare più veloce di se?), Ma ho pensato di far notare che ho fatto guardo.

Inoltre, sto testando un rilascio a costruire al di fuori di VS quindi non c'è debuggery in corso.

È stato utile?

Soluzione

Questa è una buona domanda. La versione breve è che tutto si riduce alla realizzazione del IEqualityComparer che IndexOf userà. Vediamo il seguente pezzo di codice:

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

È necessario compilarlo con csc / optimize + .

Ecco il risultato che ho:

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

Ora, cambiare il tipo di matrice e del EqualityComparer al byte, ed ecco il risultato che ho:

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

Come si può vedere, l'array di byte è speciale carter, che probabilmente è ottimizzato per trovare un byte in un array di byte. Come non posso decompilare il framework .NET, ho fermato il analizzare qui, ma credo che sia un buon indizio.

Altri suggerimenti

I file vengono mscorlib ngen'd durante l'installazione. Prova ngen'ing il file utilizzando Ngen.exe utility (fornito insieme a .NET framwork suppongo) ... e poi controllare i parametri di riferimento. Potrebbe essere leggermente più veloce.

Per rendere il codice .NET corsa a velocità quasi nativa, si consiglia pertanto di "Ngen" il codice durante l'installazione di app ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top