Question

Ceci est juste un projet personnel que j'ai creuser dans. Fondamentalement, je parse un fichier texte (par exemple de 20 Mo jusqu'à environ 1Go) en utilisant StreamReader. La performance est assez solide, mais quand même ... J'ai été démangeaisons pour voir ce qui se passerait si je parse en binaire. Ne vous méprenez pas, je ne suis pas optimiser prématurément. Je suis defintely micro-optimisation sur le but juste « pour voir ».

Alors, je lis dans le fichier texte à l'aide des tableaux d'octets. Venez découvrir, de nouvelles lignes peuvent être la norme CR / LF ou CR ou LF (Windows) ... assez en désordre. Je l'avais espéré pouvoir utiliser Array.indexOf sur CR puis passez devant le LF. Au lieu de cela, je me retrouve à écrire du code très similaire à IndexOf mais pour vérifier non plus et retourne un tableau selon les besoins.

Ainsi, le point crucial: en utilisant le code très similaire à IndexOf, mon code se termine toujours par être incroyablement lent. Pour le mettre en perspective à l'aide d'un fichier 800MB:

  • Utilisation IndexOf et recherche CR: ~ 320mb / s
  • Utilisation StreamReader et ReadLine: ~ 180 Mo / s
  • pour la boucle répliquant IndexOf: ~ 150mb / s

voici le code avec la boucle 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);
            }
        }
    }
}

est le bloc de code plus rapide (~ 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);
    }
}

(Non, ce n'est pas prêt pour la production, certains cas, il sera exploser;. J'utilise un tampon de taille de 128 Ko à ignorer la plupart des personnes)

Ma grande question est ... pourquoi Array.indexOf ne fonctionne beaucoup plus vite? Il est essentiellement le même, une boucle de marche un tableau. Y at-il quelque chose au sujet du code de mscorlib manière est exécutée? Même changer le code ci-dessus pour répliquer vraiment IndexOf et à la recherche pour juste CR puis sauter LF comme je le ferais si vous utilisez IndexOf ne permet pas. Heu ... J'ai été en passant par diverses permutations et il est assez tard que peut-être il y a un bug flagrant que je suis absent?

BTW, je regardais dans ReadLine et remarqué utilise un bloc de commutation plutôt que d'un bloc si ... quand je fais quelque chose de similaire, assez bizarrement elle augmente les performances d'environ 15 Mo / s. C'est une autre question pour une autre fois (pourquoi est-passer plus vite que si?) Mais je pensais que je voudrais souligner que je ne la regarde.

En outre, je teste une version validée en dehors de VS donc il n'y a pas debuggery passe.

Était-ce utile?

La solution

C'est une bonne question. La version courte est que tout se résume à la mise en œuvre du IEqualityComparer que IndexOf utilisera. Laissez voir le morceau de code suivant:

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

Vous aurez besoin de le compiler avec csc / + optimize .

Voici le résultat que j'ai:

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

Maintenant, changez le type du tableau et du EqualityComparer à l'octet, et voici le résultat que j'ai:

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

Comme vous pouvez le voir, le tableau d'octets est spécial tubé, qui est probablement optimisé pour trouver un octet dans un tableau d'octets. Comme je ne peux pas décompiler le cadre de .net, je me suis arrêté l'Analysons ici, mais je suppose que c'est une assez bonne idée.

Autres conseils

Les fichiers mscorlib sont ngen'd lors de l'installation. Essayez ngen'ing votre fichier en utilisant l'utilitaire Ngen.exe (fourni avec framwork .NET je suppose) ... puis vérifiez les points de repère. Peut-être un peu plus rapide.

Pour faire fonctionner votre code .NET à une vitesse quasi-native, Microsoft vous recommande de "Ngen" votre code lors de l'installation de l'application ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top