Anyway zu Parallelertrag c #

8

Ich habe mehrere Enumeratoren, die über flache Dateien aufzählen. Ich hatte ursprünglich jeden Enumerator in einem parallelen Aufruf und jede Aktion fügte einem BlockingCollection<Entity> hinzu, und diese Sammlung gab ein ConsumingEnumerable ();

zurück %Vor%

Nach ein paar Tests stellt sich jedoch heraus, dass die Änderung des Codes etwa 20-25% schneller ist.

%Vor%

Das Problem mit der Codeänderung ist, dass es wartet, bis alle Flat-File-Abfragen aufgelistet sind, bevor es das gesamte Los zurückgibt, das dann aufgelistet und zurückgegeben werden kann.

Wäre es möglich, das obige Bit des Codes irgendwie nachzugeben, um es noch schneller zu machen?

Ich sollte hinzufügen, dass die kombinierten Ergebnisse aller Flat-File-Abfragen höchstens 1000 Entitäten sind.

Bearbeiten : Es ändert sich nicht in die unten angegebene Zeit für die Laufzeit. (R # schlägt sogar vor, zurück zu gehen, wie es war)

%Vor%     
Mark Vickery 14.11.2012, 15:32
quelle

3 Antworten

4
  

Das Problem mit der Codeänderung ist, dass es wartet, bis alle Flat-File-Abfragen aufgelistet sind, bevor es das gesamte Los zurückgibt, das dann aufgelistet und zurückgegeben werden kann.

Beweisen wir, dass es an einem einfachen Beispiel falsch ist. Lassen Sie uns zunächst eine TestQuery -Klasse erstellen, die nach einer bestimmten Zeit eine einzelne Entität ergibt. Zweitens, lassen Sie uns mehrere Testabfragen parallel ausführen und messen Sie, wie lange es dauerte, um ihr Ergebnis zu erzielen.

%Vor%

Dieser Code wird gedruckt:

  

Nach 1 Sekunde angezeigt   Nach 2 Sekunden angezeigt   Nach 3 Sekunden angezeigt.

Sie können mit dieser Ausgabe sehen, dass AsParallel() jedes Ergebnis liefert, sobald es verfügbar ist, so dass alles gut funktioniert. Beachten Sie, dass Sie je nach Grad der Parallelität unterschiedliche Timings erhalten können (z. B. "2s, 5s, 6s" mit einem Parallelitätsgrad von 1, wodurch die gesamte Operation überhaupt nicht parallel verläuft). Diese Ausgabe kommt von einem 4-Kern-Rechner.

Ihre lange Verarbeitung wird wahrscheinlich mit der Anzahl der Kerne skalieren, wenn zwischen den Threads kein gemeinsamer Engpass vorhanden ist (z. B. eine freigegebene gesperrte Ressource). Vielleicht möchten Sie Ihren Algorithmus profilieren, um zu sehen, ob es langsame Teile gibt, die mit Tools wie dotTrace verbessert werden können.

>     
Julien Lebosquain 14.11.2012, 16:52
quelle
2

Ich glaube nicht, dass irgendwo in Ihrem Code eine rote Fahne steht. Es gibt keine empörenden Ineffizienzen. Ich denke, es kommt auf mehrere kleinere Unterschiede an.

PLINQ ist sehr gut in der Verarbeitung von Datenströmen. Intern funktioniert es effizienter als Elemente einer synchronisierten Liste einzeln hinzuzufügen. Ich vermute, dass Ihre Aufrufe von TryAdd ein Engpass sind, da jeder Aufruf intern mindestens 2 Interlocked -Operationen erfordert. Diese können den Interprozessor-Speicherbus enorm belasten, da alle Threads um die gleiche Cache-Zeile konkurrieren.

PLINQ ist billiger, weil intern gepuffert wird. Ich bin mir sicher, dass es nicht einzeln ausgegeben wird. Wahrscheinlich werden sie stapelweise und amortisiert die Kosten für die Sychronisierung auf diese Weise über mehrere Artikel.

Ein zweites Problem wäre die begrenzte Kapazität von BlockingCollection . 100 ist nicht viel. Dies könnte zu viel Warten führen. Warten ist teuer, weil es einen Aufruf an den Kernel und einen Kontextwechsel erfordert.

    
usr 14.11.2012 17:24
quelle
1

Ich mache diese Alternative, die mir in jedem Szenario gut tut:

Das funktioniert für mich:

  • In einer Task in einer Parallel.Foreach Enqueue in einer ConcurrentQueue das Element transformiert, um verarbeitet zu werden.
  • Die Aufgabe hat eine Fortsetzung, die ein Zeichen enthält Flag mit dieser Aufgabe endet.
  • Im selben Thread der Ausführung mit Aufgaben endet eine Weile aus der Warteschlange und ergibt

Schnelle und ausgezeichnete Ergebnisse für mich:

%Vor%     
quelle

Tags und Links