2009-05-21 5 views
4

Dies ist nur ein persönliches Projekt, in das ich gegraben habe. Im Grunde analysiere ich eine Textdatei (zB von 20 MB bis zu 1 GB) mit StreamReader. Die Leistung ist ziemlich solide, aber immer noch ... Ich habe es juckt zu sehen, was passieren würde, wenn ich es binär analysieren würde. Versteh mich nicht falsch, ich optimiere nicht vorzeitig. Ich bin auf jeden Fall Mikro-Optimierung absichtlich "nur um zu sehen".Warum ist System/mscorlib so viel schneller? Vor allem für Schleifen?

Also, ich lese in der Textdatei mit Byte-Arrays. Kommen Sie, um herauszufinden, neue Zeilen können die (Windows) Standard CR/LF oder CR oder LF ... ziemlich chaotisch sein. Ich hatte gehofft, in der Lage zu sein, Array.IndexOf auf CR zu verwenden und dann hinter dem LF zu überspringen. Stattdessen schreibe ich Code, der IndexOf sehr ähnlich ist, aber nach einem von beiden sucht und ein Array nach Bedarf zurückgibt.

Also die Crux: mit sehr ähnlichen Code zu IndexOf, mein Code immer noch am Ende wird wahnsinnig langsamer. Um es in Perspektive zu setzen eine 800MB-Datei mit:

  • IndexOf verwenden und suchen nach CR: ~ 320 MB/s
  • Mit Stream und Readline: ~ 180 MB/s
  • for-Schleife replizierende IndexOf: ~ 150 MB/s

hier ist der Code mit dem for-Schleife (~ 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); 
      } 
     } 
    } 
} 

dies ist der schnellere Codeblock (~ 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); 
    } 
} 

(Nein, es ist nicht produktionsfertig, bestimmte Fälle werden es sprengen; Ich benutze einen 128kb großen Puffer, um die meisten davon zu ignorieren.)

Also meine große Frage ist ... warum funktioniert Array.IndexOf so viel schneller? Es ist im Wesentlichen das Gleiche, eine for-Schleife, die ein Array durchläuft. Gibt es etwas über die Art, wie der mscorlib-Code ausgeführt wird? Sogar das Ändern des obigen Codes, um IndexOf wirklich zu replizieren und nur nach CR zu suchen und dann LF zu überspringen, wie ich es bei Verwendung von IndexOf tun würde, hilft nicht. Errr ... Ich habe verschiedene Permutationen durchgespielt und es ist spät genug, dass es vielleicht einen eklatanten Fehler gibt, den ich vermisse?

BTW, ich habe in ReadLine geschaut und bemerkt, dass es einen Switch-Block anstelle eines if-Blockes benutzt ... wenn ich etwas Ähnliches mache, komischerweise erhöht es die Leistung um ungefähr 15mb/s. Das sind andere Fragen für eine andere Zeit (warum ist Schalter schneller als wenn?), Aber ich dachte, ich würde darauf hinweisen, dass ich es mir angeschaut habe.

Außerdem teste ich einen Release-Build außerhalb von VS, so dass es keine Debuggerei gibt.

+0

Hmm, interessant. Wenn ich mir Reflector anschaue, um die mscorlib-Variante zu sehen, sehe ich dort keine Tricks, die es benutzen könnte. – Joey

+0

Es endete tatsächlich mit Tricks. IEqualityComparer .Default endete mit ByteEqualityComparer. Ich habe es auch nicht bemerkt, seit es eine Standardimplementierung für IndexOf gab. Prüfe die Default-Eigenschaft und es wird ein Shell-Spiel gemacht und im Spezialfall für Byte getauscht. –

Antwort

2

, dass eine gute Frage. Die kurze Version ist, dass alles auf die Implementierung des IEqualityComparer läuft, den IndexOf verwenden wird. Lassen Sie das folgende Stück Code sehen:

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

Sie werden es + mit csc/optimize zu kompilieren.

Hier ist das Ergebnis, das ich habe:

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

nun den Typ des Arrays ändern und des EqualityComparer auf Byte, und hier ist das Ergebnis, das ich habe:

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

Wie Sie sehen können , das Byte-Array ist speziell verkleidet, was wahrscheinlich optimiert ist, um ein Byte in einem Byte-Array zu finden. Da ich das .NET-Framework nicht dekompilieren kann, habe ich hier die Analyse gestoppt, aber ich denke, das ist ein ziemlich guter Anhaltspunkt.

+0

Bingo! Es endete mit ByteEqualityComparer, die wiederum Puffer mit Zeigern verwendet. Tun Sie etwas ähnliches, ich jetzt ~ 350mb/s. Muss sagen, ich bin ziemlich enttäuscht in der verwalteten Leistung. Ich bin mir nicht sicher, ob ich wirklich in dieses kleine Projekt einsteigen möchte. –

2

Die mscorlib-Dateien werden während der Installation erstellt. Versuchen Sie, Ihre Datei mit dem Ngen.exe-Dienstprogramm (zusammen mit .NET-Framework vorausgesetzt, nehme ich an) ... und überprüfen Sie dann die Benchmarks. Könnte etwas schneller sein.

Um Ihre .NET-Code laufen bei nahezu nativer Geschwindigkeit zu machen, empfiehlt Microsoft, dass Sie „Ngen“ Code während der App-Installation ...

+2

ngen tut eigentlich nichts anderes als der Just-in-Time-Compiler. Der Unterschied ist, dass es den Job im Voraus erledigt. (So ​​würden die gleichen Ergebnisse erwartet werden, wenn der Code das zweite Mal ausgeführt wird ... Denn dann ist es bereits vorkompiliert.) –

Verwandte Themen