Was kann ich tun, damit diese Schleife schneller läuft?

8

Ich habe diese einfache Schleife:

%Vor%

Ich habe seine Leistung mit seiner C ++ - Version verglichen. Ich denke allerdings, dass die Performance in etwa gleich sein sollte, da es sich um einen sehr einfachen Code handelt und die Range-Checks in diesem Fall entfallen. Aber es stellte sich heraus, dass die C ++ Version fast dreimal schneller ist. Also habe ich C # unsicher Version implementiert, aber die Leistung war noch schlimmer. Resharper schlägt vor, die Schleife wie folgt in linq Ausdruck umzuwandeln:

%Vor%

Dieser Code ist viel langsamer als die ursprüngliche Schleife in C #

Könnte mir jemand sagen, ob es noch etwas gibt, was ich tun kann, um die Leistung dieser Schleife zu verbessern (ohne sie in 64-Bit-Version zu kompilieren - was zwei mal schneller ist).

Alle Tests wurden mit 32-Bit-Version durchgeführt und ohne Debugger ausgeführt.

Bearbeiten: Kleine Korrektur. 64-Bit-Version ist doppelt so schnell mit Doppel, nicht ints

    
userx01233433 13.10.2013, 16:00
quelle

7 Antworten

8

Wickeln Sie die Schleife 2-8 Mal aus. Messen Sie, welches ist das Beste. Das .NET JIT optimiert schlecht, also müssen Sie etwas von seiner Arbeit tun.

Wahrscheinlich müssen Sie auch unsafe hinzufügen, da der JIT die Überprüfung der Array-Grenzen jetzt nicht optimieren kann.

Sie können auch versuchen, in mehrere Summenvariablen zu aggregieren:

%Vor%

Das könnte die Parallelität auf Befehlsebene erhöhen, weil alle Anweisungen add jetzt unabhängig sind.

Das i+0 wird automatisch auf i optimiert.

Ich habe es getestet und es hat ungefähr 30% abgeschabt.

Die Zeiten sind stabil, wenn sie wiederholt werden. Code:

%Vor%

Weiter herumspielen, es stellt sich heraus, dass mehrere Aggregationsvariablen nichts tun. Das Abwickeln der Schleife hat jedoch eine wesentliche Verbesserung bewirkt. Unsicher hat nichts gemacht (außer im abgewickelten Fall, wo es ziemlich benötigt wird). 2 mal abrollen ist so gut wie 4.

Wird auf einem Core i7 ausgeführt.

    
usr 13.10.2013, 16:34
quelle
15
%Vor%

Linq Parallel scheint zumindest auf meiner Maschine gefastet zu haben.

Das Festlegen der Länge spielt keine große Rolle, verbessert aber ~ 10%

Es gibt nicht viel, was Sie tatsächlich tun könnten, unmanaged C-Code wird dafür immer schneller sein.

Ergebnisse auf meinem PC sind:

%Vor%     
MichaC 13.10.2013 16:10
quelle
5

Zuerst ein paar allgemeine Bemerkungen über Mikro-Benchmarks wie folgt:

  • Die Zeiten hier sind so kurz, dass die JIT-Zeit erheblich sein kann. Dies ist wichtig, weil eine parallele ForEach -Schleife einen anonymen Delegaten enthält, der beim ersten Aufruf nur JIT-aktiv ist und daher die JIT-Zeit in das Timing einbezogen wird, wenn der Benchmark zum ersten Mal ausgeführt wird.
  • Der Kontext des Codes ist ebenfalls wichtig. Der JITter kann kleinere Funktionen besser optimieren. Die Isolation des Benchmark-Codes in seiner eigenen Funktion kann sich erheblich auf die Performance auswirken.

Es gibt vier grundlegende Techniken, um den Code zu beschleunigen (wenn wir ihn rein CLR behalten):

  1. Parallel dazu. Das ist offensichtlich.
  2. Loops ausrollen. Dies reduziert die Anzahl der Anweisungen, indem nur alle 2 oder mehr Iterationen ein Vergleich durchgeführt wird.
  3. Verwenden von unsicherem Code. Dies ist in diesem Fall kein großer Vorteil, da das Hauptproblem (Bereichsüberprüfungen auf dem Array) weg optimiert wird.
  4. Lassen Sie den Code besser optimieren. Wir können dies tun, indem wir den eigentlichen Benchmark-Code in eine separate Methode einfügen.

Hier ist der parallele Code:

%Vor%

Die Partitioner -Klasse lebt im Namensraum System.Collections.Concurrent .

Auf meinem Rechner (i7 950, 8 logische Kerne) waren die folgenden Timings:

%Vor%

Es gab keinen signifikanten Unterschied zwischen 32-Bit- und 64-Bit-Code.

    
Jeffrey Sax 13.10.2013 19:43
quelle
0

Ich habe zu @ Elas 'Antwort folgendes hinzugefügt:

%Vor%

und dann die Ergebnisse gedruckt, so dass Sie Referenzwerte erhalten:

%Vor%

Wie Sie sehen können, waaay schneller :) Für Stepsize probierte ich arr.Length / 8 , arr.Length / 16 , arr.Length / 32 (ich habe i7 cpu (4 Kerne * 2 Threads = 8 Threads gleichzeitig)) und sie waren alle ziemlich gleich, also ist es deine Entscheidung

Edit: Ich habe auch stepsize = arr.length / 100 versucht, was irgendwo bei 250ms ist, also ein bisschen langsamer.

    
Nefarion 13.10.2013 17:17
quelle
0

Die Verwendung von unmittelbaren Operanden verbessert die Leistung in gewissem Maße,

%Vor%

Der obige Code muss auf zwei Speicherplätze zugreifen, d. h. int i und array.length;

Verwenden Sie stattdessen

%Vor%     
Nakul Patekar 13.10.2013 21:32
quelle
0

Unsicherer und paralleler Code sollte ebenfalls die Leistung erhöhen. Überprüfen Sie diesen Artikel, um mehr zu erfahren.

Optimieren Sie es.

    
user955970 20.10.2013 10:16
quelle
0

Eine einfache und manchmal signifikante C # for -Schleifenoptimierung, die oft übersehen wird, ist das Wechseln des Schleifenzähler-Variablentyps von int nach uint . Dies führt zu einer durchschnittlichen Beschleunigung von ca. 12% für Ihre standardmäßigen i++ ( Inkrement ) Schleifen mit Millionen von Iterationen. Wenn Ihre Schleife weniger als iteriert, wird die Leistung wahrscheinlich nicht viel ändern.

Beachten Sie, dass Arrays mit uint indiziert werden können, ohne dass sie auf int umgesetzt werden, damit Sie bei der Indexierung innerhalb der Schleife keinen der Vorteile verlieren. Die einzigen allgemeinen Gründe, diese Technik nicht zu verwenden, sind, wenn Sie negative Schleifenzählerwerte benötigen oder wenn die Schleifenzählervariable für andere Funktionsaufrufe usw. innerhalb der Schleife in int umgewandelt werden muss. Sobald du Cast machen musst, ist es das wahrscheinlich nicht wert.

    
Special Sauce 18.12.2015 21:52
quelle

Tags und Links