2009-05-21 5 views
4

Ceci est juste un projet personnel que j'ai été creuser dans. Fondamentalement, j'analyse un fichier texte (disons de 20 Mo jusqu'à environ 1 Go) en utilisant StreamReader. La performance est assez solide, mais quand même ... J'ai été impatient de voir ce qui se passerait si je l'analysais en binaire. Ne vous méprenez pas, je n'optimise pas prématurément. Je suis définitivement optimisant micro juste pour "voir". Donc, je lis dans le fichier texte en utilisant des tableaux d'octets. Venez découvrir, les nouvelles lignes peuvent être le (Windows) standard CR/LF ou CR ou LF ... assez salissant. J'avais espéré être capable d'utiliser Array.IndexOf sur CR, puis passer le LF. Au lieu de cela je me trouve en train d'écrire du code très similaire à IndexOf mais en vérifiant pour et renvoyer un tableau si nécessaire. Donc le crux: en utilisant un code très similaire à IndexOf, mon code finit toujours par être incroyablement plus lent. Pour le mettre en perspective à l'aide d'un fichier 800MB:Pourquoi le code System/mscorlib est-il tellement plus rapide? Surtout pour les boucles?

  • aide IndexOf et recherche CR: ~ 320mb/s
  • En utilisant StreamReader et ReadLine: ~ 180 Mo/s
  • boucle répliquant IndexOf: ~ de 150mb/s

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

c'est le bloc de code plus rapide (~ 320m b/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 vont le faire exploser; J'utilise un tampon de taille 128kb pour ignorer la plupart de ceux-ci.)

Donc ma grande question est ... pourquoi Array.IndexOf fonctionne-t-il beaucoup plus vite? C'est essentiellement la même chose, une boucle pour un tableau. Y a-t-il quelque chose à propos de la façon dont le code mscorlib est exécuté? Même en changeant le code ci-dessus pour vraiment répliquer IndexOf et en cherchant juste CR, puis en sautant LF comme je le ferais si j'utilise IndexOf n'aide pas. Euh ... j'ai traversé plusieurs permutations et il est assez tard pour qu'il y ait peut-être un bug qui me manque?

BTW, j'ai regardé dans ReadLine et a remarqué qu'il utilise un bloc de commutation plutôt qu'un bloc if ... quand je fais quelque chose de similaire, bizarrement, il augmente les performances d'environ 15mb/s. C'est une autre question pour une autre fois (pourquoi est-ce que le changement est plus rapide que si?) Mais je me suis dit que je ferais remarquer que je l'ai regardé.

En outre, je teste une version de publication en dehors de VS afin qu'il n'y ait pas de débogage.

+0

Hmm, intéressant. En utilisant Reflector pour regarder la variante de mscorlib, je ne vois pas de supercherie qu'il pourrait utiliser. – Joey

+0

Il a fini par utiliser la ruse. IEqualityComparer .Default a fini par utiliser ByteEqualityComparer. Je ne l'ai pas remarqué d'abord car il y avait une implémentation par défaut pour IndexOf. Vérifiez la propriété par défaut et il fait un jeu de shell et échange dans le cas spécial pour byte. –

Répondre

2

C'est une bonne question. La version courte est que tout se résume à l'implémentation de l'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.

est ici 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, changer 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, probablement optimisé pour trouver un octet dans un tableau d'octets. Comme je ne peux pas décompiler le framework .net, j'ai arrêté l'analyse ici, mais je suppose que c'est un très bon indice.

+0

Bingo! Il a fini par utiliser ByteEqualityComparer qui à son tour utilisait Buffer en utilisant des pointeurs. En faisant quelque chose de similaire, je frappe maintenant ~ 350mb/s. Doit dire, je suis plutôt déçu de la performance gérée. Je ne sais pas si je veux vraiment tomber dans un endroit dangereux pour ce petit projet. –

2

Les fichiers mscorlib sont endommagés lors de l'installation. Essayez ngen'ing votre fichier en utilisant l'utilitaire Ngen.exe (fourni avec .NET framwork je suppose) ... puis vérifiez les tests de performance. Pourrait être légèrement plus rapide.

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

+2

ngen ne fait vraiment rien d'autre que le compilateur juste-à-temps. La différence est qu'il fait le travail à l'avance. (Donc, les mêmes résultats seraient attendus lors de l'exécution du code la deuxième fois ... Parce que c'est déjà pré-compilé.) –

Questions connexes