[Übersetzung] Wann werden parallele Streams verwendet?

    Quellenautoren
    : Doug Lea mit Brian Goetz, Paul Sandoz, Aleksey Shipilev, Heinz Kabutz, Joe Bowbeer, ...

    Das Framework java.util.streamsenthält datengesteuerte Operationen für Sammlungen und andere Datenquellen. Die meisten Stream-Methoden führen für jedes Element dieselbe Operation aus. Mit der Sammlungsmethode können datengesteuertparallelStream() in Gegenwart mehrerer Kerne datenparallel werden . Aber wann lohnt es sich?


    Verwenden Sie S.parallelStream().operation(F)stattdessen die Verwendung S.stream().operation(F), vorausgesetzt, die Operationen sind voneinander unabhängig und kostenaufwändig oder werden auf eine große Anzahl von Elementen einer effektiv teilbaren Datenstruktur oder auf beide angewendet. Genauer gesagt:


    • F: Die Funktion zum Arbeiten mit einem Element, normalerweise Lambda, ist unabhängig, d. h. Das Durchführen eines Vorgangs für eines der Elemente ist nicht von den Vorgängen mit anderen Elementen abhängig und hat keine Auswirkungen auf sie (Empfehlungen zur Verwendung unabhängiger (nicht störender) zustandsloser Funktionen finden Sie in der Dokumentation zum Stream- Paket ).
    • S: Die Quellensammlung wird effektiv aufgeteilt. Neben Sammlungen gibt es noch andere stromlinienförmige Datenquellen, die sich beispielsweise java.util.SplittableRandomfür die Parallelisierung eignen (für die Parallelisierung Sie die Methode verwenden können stream.parallel()). Die meisten Quellen mit E / A an der Basis sind jedoch hauptsächlich für den sequenziellen Betrieb ausgelegt.
    • Die Gesamtzeit im sequentiellen Modus überschreitet die minimal zulässige Grenze. Für die meisten Plattformen liegt der Grenzwert heute (innerhalb von 10) ungefähr bei 100 Mikrosekunden. Genaue Messungen sind in diesem Fall nicht erforderlich. Für praktische Zwecke reicht es aus, Ndie Anzahl der Elemente einfach mit Q(der Zeit, die es dauert, um mit einem Element zu arbeiten) zu multiplizieren F, und QSie können die Anzahl der Operationen oder die Anzahl der Codezeilen grob schätzen. Danach müssen Sie überprüfen, ob N * Qmindestens ein geringerer 10000Wert vorhanden ist (wenn Sie ein Feigling sind, fügen Sie dann eine oder mehrere Nullen hinzu). Wenn also F- dies ist eine kleine Funktion wie - x -> x + 1, dann macht die parallele Ausführung einen Sinn, wenn N >= 10000. Umgekehrt, wennF- Dies ist eine gewichtige Berechnung. Wie beim Finden des nächstbesten Zuges in einem Schachspiel ist der Wert Qso groß, dass er Nvernachlässigt werden kann, solange die Sammlung jedoch vollständig aufgeteilt ist.

    Das Stream-Verarbeitungs-Framework wird (und kann) auf keinen der obigen Punkte bestehen. Wenn die Berechnungen voneinander abhängig sind, ist ihre parallele Ausführung nicht sinnvoll, oder sie ist überhaupt schädlich und führt zu Fehlern. Andere Kriterien, die aus den oben genannten Kriterien für technische Einschränkungen (Probleme) und Kompromisse (Tradeoffs) abgeleitet werden, sind:


    • Startup
      Das Auftreten zusätzlicher Cores in Prozessoren wurde in den meisten Fällen durch das Hinzufügen eines Energieverwaltungsmechanismus begleitet, der zu einem langsameren Start von Cores führen kann, manchmal mit zusätzlichen Überlagerungen von JVM, Betriebssystem und Hypervisor. In diesem Fall entspricht die Grenze, bei der der Parallelmodus sinnvoll ist, ungefähr der Zeit, die erforderlich ist, um die Verarbeitung von Unteraufgaben mit einer ausreichenden Anzahl von Kernen zu beginnen. Danach kann paralleles Rechnen energieeffizienter sein als sequenziell (abhängig von den Details der Prozessoren und Systeme. Ein Beispiel finden Sie in diesem Artikel ).
    • Detaillierung (Granularität)
      Selten macht es Sinn, kleine Berechnungen aufzuteilen. Das Framework teilt die Aufgabe normalerweise so auf, dass die einzelnen Teile auf allen verfügbaren Kernen des Systems arbeiten können. Wenn es nach dem Start fast keine Arbeit für jeden Kern gibt, werden (normalerweise sequentielle) Anstrengungen zur Organisation des Parallel-Computing vergeudet. Wenn man bedenkt, dass in der Praxis die Anzahl der Kerne im Bereich von 2 bis 256 liegt, wird auch der unerwünschte Effekt einer übermäßigen Aufteilung der Aufgabe verhindert.
    • Teilbarkeit (Aufteilbarkeit)
      Zu den am effizientesten aufgeteilten Sammlungen gehören ArrayListund {Concurrent}HashMapebenso regelmäßige Arrays ( T[]die mit statischen Methoden in Teile unterteilt sind java.util.Arrays). Die am wenigsten wirksam sind spaltbare LinkedList, BlockingQueueund die meisten Quellen mit I / O in der Basis. Der Rest ist irgendwo in der Mitte (Datenstrukturen, die wahlfreien Zugriff und / oder effiziente Suche unterstützen, werden normalerweise effizient aufgeteilt). Wenn die Datenfragmentierung länger dauert als die Verarbeitung, sind die Bemühungen umsonst. Wenn es Qgroß genug ist, können Sie durch Parallelisierung sogar einen Gewinn erzielenLinkedListDies ist jedoch ein seltener Fall. Darüber hinaus können einige Quellen nicht in ein einzelnes Element aufgeteilt werden, sodass der Zersetzungsgrad des Problems möglicherweise begrenzt ist.

    Die genauen Eigenschaften dieser Effekte zu erhalten, kann schwierig sein (wenn auch, wenn Sie es versuchen und dies mit Tools wie JMH tun ). Der kumulative Effekt ist jedoch ziemlich leicht zu bemerken. Um es selbst zu erleben, führen Sie ein Experiment durch. Zum Beispiel auf einer 32-Kern-Testmaschine, wenn Sie kleine Funktionen wie max()oder sum()darüber startenArrayListBreak-Even-Punkt liegt bei etwa 10.000. Bei mehr Elementen wird die Beschleunigung bis zu 20 Mal notiert. Die Arbeitszeit für Sammlungen mit weniger als 10.000 Artikeln ist nicht viel geringer als für 10.000 und daher langsamer als die sequentielle Verarbeitung. Das schlechteste Ergebnis tritt mit weniger als 100 Elementen auf - in diesem Fall hören die beteiligten Threads auf, nichts Nützliches mehr zu tun, weil Berechnungen werden abgeschlossen, bevor sie beginnen. Auf der anderen Seite sind die Vorgänge bei Artikeln, die zeitaufwändig sind, im Falle einer effizienten und vollständig aufgeteilten Sammlung, wie ArrayListdie Vorteile, sofort sichtbar.


    Zusammenfassend kann gesagt werden, dass die Verwendung parallel()im Fall eines zu geringen Rechenaufwands etwa 100Mikrosekunden kostet , und die Verwendung sollte zumindest diese Zeit (oder möglicherweise Stunden für sehr große Aufgaben) einsparen. Die spezifischen Kosten und Vorteile variieren mit der Zeit und für verschiedene Plattformen sowie je nach Kontext. Zum Beispiel erhöht die parallele Einführung kleiner Berechnungen innerhalb eines sequentiellen Zyklus die Wirkung von Anstieg und Abfall (Leistungsmikrotests, bei denen sich dies manifestiert, spiegeln möglicherweise nicht die tatsächliche Situation wider).


    Fragen und Antworten


    • Warum weiß die JVM selbst nicht, wann Operationen im Parallelmodus ausgeführt werden sollen?

    Sie könnte es versuchen, aber zu oft wäre die Entscheidung falsch. Die Suche nach einer vollautomatischen Multi-Core-Parallelität hat in den letzten dreißig Jahren nicht zu einer universellen Lösung geführt. Daher verwendet das Framework einen robusteren Ansatz, bei dem der Benutzer nur " Ja" oder "Nein" wählen muss. Diese Wahl beruht auf Konstruktionsproblemen, die ständig bei der sequentiellen Programmierung auftreten und die wahrscheinlich nicht vollständig verschwinden. Wenn Sie beispielsweise nach dem Maximalwert in einer Auflistung suchen, die ein einzelnes Element enthält, können Sie eine 100-fache Verlangsamung feststellen, indem Sie diesen Wert direkt (ohne Auflistung) verwenden. Manchmal kann die JVM solche Fälle für Sie optimieren. Dies geschieht jedoch selten in aufeinanderfolgenden Fällen und niemals im Parallelmodus. Andererseits kann davon ausgegangen werden, dass die Tools den Anwendern bei ihrer Entwicklung helfen, bessere Entscheidungen zu treffen.


    • Was passiert , wenn für eine gute Entscheidung zu treffen habe ich nicht genug Wissen über die Parameter ( F, N, Q, S)?

    Auch dies ist ähnlich wie bei der sequentiellen Programmierung. Beispielsweise wird eine S.contains(x)Klassenmethode Collectionnormalerweise schnell ausgeführt, wenn Sdies zutrifft HashSet, langsam LinkedListund in anderen Fällen mäßig. Normalerweise ist es für den Autor einer Komponente, die eine Sammlung verwendet, der beste Weg, um diese Situation zu kapseln und nur eine bestimmte Operation darauf zu veröffentlichen. Die Benutzer werden dann von der Auswahl getrennt. Gleiches gilt für den Parallelbetrieb. Zum Beispiel kann eine Komponente mit einer internen Sammlung von Preisen kann die Methode der Überprüfung seine Größe bestimmt die Grenze zu erreichen , die sinnvoll sein werden , bis element Berechnungen werden nicht zu teuer. Beispiel:


    publiclonggetMaxPrice(){ return priceStream().max(); }
    private Stream priceStream(){
     return (prices.size() < MIN_PAR) ? 
       prices.stream() : prices.parallelStream();
    }

    Diese Idee kann auf andere Überlegungen zum Zeitpunkt und zur Verwendung von Parallelität erweitert werden.


    • Was ist, wenn meine Funktion E / A oder synchronisierte Vorgänge ausführen kann?

    Ein Extremfall sind Funktionen, die die Unabhängigkeitskriterien nicht erfüllen, einschließlich aufeinanderfolgender E / A-Vorgänge in der Natur, Zugriff auf das Blockieren synchronisierter Ressourcen und Fälle, in denen ein Fehler in einer parallelen Subtask, der E / A ausführt, andere beeinträchtigt. Ihre Parallelisierung macht nicht viel Sinn. Auf der anderen Seite gibt es Berechnungen, die gelegentlich E / A-Operationen ausführen oder die Synchronisation selten blockieren (z. B. die meisten Protokollierungsfälle und die Verwendung von Konkurrenzsammlungen)ConcurrentHashMap). Sie sind harmlos. Was zwischen ihnen ist, erfordert mehr Forschung. Wenn jede Teilaufgabe während des Wartens auf E / A oder Zugriff eine beträchtliche Zeit blockieren kann, sind die CPU-Ressourcen im Leerlauf, ohne dass sie vom Programm oder der JVM verwendet werden können. Von so einem schlechten alles. In diesen Fällen ist die parallele Stream-Verarbeitung nicht immer die richtige Wahl. Es gibt jedoch gute Alternativen - zum Beispiel asynchrone E / A und Vorgehensweise CompletableFuture.


    • Was ist, wenn meine Quelle auf E / A basiert?

    Derzeit werden Stream(zum Beispiel BufferedReader.lines()) JDK- E / A-Generatoren hauptsächlich für die sequentielle Verwendung angepasst, wobei die Elemente bei ihrem Eintreffen einzeln verarbeitet werden. Unterstützung für die Hochleistungs-Massenverarbeitung (Massenverarbeitung) von gepufferten E / A ist möglich, dies erfordert jedoch derzeit die Entwicklung spezieller Generatoren Streams, Spliterators und Collectors. In einigen JDK-Versionen wird möglicherweise Unterstützung für einige häufig auftretende Fälle hinzugefügt.


    • Was ist, wenn mein Programm auf einem geladenen Computer ausgeführt wird und alle Kerne ausgelastet sind?

    Maschinen verfügen normalerweise über eine feste Anzahl von Kernen und können bei parallelen Operationen keine neuen magischen Kerne erstellen. Doch bis die parallelen Modus Auswahlkriterien deutlich sprechen fürEs gibt nichts zu zweifeln. Ihre parallelen Aufgaben konkurrieren mit der CPU für andere und Sie werden weniger Beschleunigung feststellen. In den meisten Fällen ist es noch effektiver als andere Alternativen. Der zugrunde liegende Mechanismus ist so ausgelegt, dass Sie, wenn keine Cores verfügbar sind, im Vergleich zur sequentiellen Option nur eine geringfügige Verlangsamung bemerken, es sei denn, das System ist so überlastet, dass es die ganze Zeit damit verbringt, den Kontext zu wechseln, anstatt echte Arbeit zu erledigen konfiguriert mit der Erwartung, dass die gesamte Verarbeitung sequentiell ausgeführt wird. Wenn Sie über ein solches System verfügen, hat der Administrator möglicherweise die Verwendung von Multithreading / Nuclear in den JVM-Einstellungen deaktiviert. Wenn Sie selbst der Systemadministrator sind, ist es sinnvoll, dies zu tun.


    • Werden alle Operationen parallelisiert, wenn der Parallelmodus verwendet wird?

    Ja Zumindest zu einem gewissen Grad. Es muss jedoch berücksichtigt werden, dass das Stream-Framework bei der Auswahl der Methoden die Einschränkungen von Quellen und Methoden berücksichtigt. Im Allgemeinen gilt: Je weniger Einschränkungen, desto größer ist das Potenzial für Parallelität. Auf der anderen Seite gibt es keine Garantie dafür, dass das Framework alle verfügbaren Möglichkeiten für Parallelität identifiziert und anwendet. In einigen Fällen können Sie mit Ihrer eigenen Lösung die Möglichkeiten der Parallelität besser ausnutzen.


    • Welche Beschleunigung bekomme ich durch Parallelität?

    Wenn Sie diese Tipps befolgen, ist es in der Regel ausreichend, um einen Sinn zu ergeben. Vorhersagbarkeit ist nicht die Stärke moderner Hardware und Systeme, und daher gibt es keine universelle Antwort. Der Cache-Speicherort, die GC-Merkmale, die JIT-Kompilierung, Speicherzugriffskonflikte, der Datenstandort, die Betriebsrichtlinien für das Betriebssystem und die Anwesenheit des Hypervisors sind einige der Faktoren, die einen erheblichen Einfluss haben. Die Performance des sequentiellen Modus unterliegt auch ihrem Einfluss, der bei der Verwendung von Parallelität häufig noch verschärft wird: Ein Problem, das bei sequentieller Ausführung einen Unterschied von 10 Prozent verursacht, kann zu einem 10-fachen Unterschied in der Parallelverarbeitung führen.


    Das Stream-Framework enthält einige Funktionen, die die Beschleunigungschancen erhöhen. Beispielsweise hat die Verwendung der Spezialisierung für Grundelemente, wie z. B. IntStream, normalerweise eine größere Wirkung für den Parallelmodus als für den Serienmodus. Der Grund ist, dass in diesem Fall nicht nur der Ressourcenverbrauch (und der Arbeitsspeicher) reduziert wird, sondern auch der Cache-Speicherort verbessert wird. Die Verwendung ConcurrentHashMapstattdessen reduziert HashMapbei einem Parallelbetrieb des Vorgangs collectdie internen Kosten. Wenn Sie Erfahrungen mit dem Framework sammeln, werden neue Tipps und Empfehlungen angezeigt.


    • Das alles ist zu gruselig! Können wir nur Regeln für die Verwendung von JVM-Eigenschaften zum Deaktivieren der Parallelität aufstellen?

    Wir möchten Ihnen nicht sagen, was Sie tun sollen. Das Auftauchen neuer Wege, um etwas falsch zu machen, kann für den Programmierer beängstigend sein. Fehler in Code, Architektur und Bewertungen treten natürlich auf. Vor einigen Jahrzehnten hatten einige Leute vorhergesagt, dass eine Parallelität auf Anwendungsebene zu mehr Problemen führen würde. Aber es ist nie wahr geworden.


    Jetzt auch beliebt: