[JAVA] Punkt 48: Seien Sie vorsichtig, wenn Sie Streams parallel schalten

48. Seien Sie vorsichtig, wenn Sie die Stream-Verarbeitung parallel durchführen

Die parallele Verarbeitung ist immer noch schwierig

Mit der Entwicklung von Java ist die parallele Programmierung einfacher zu schreiben, aber es ist immer noch schwierig, eine korrekte und schnelle Implementierung der parallelen Verarbeitung zu schreiben. Dies ist keine Ausnahme von der Verarbeitung von Streams in parallelen Streams. Schauen wir uns das Beispiel von Item45 an.

package tryAny.effectiveJava;

import static java.math.BigInteger.*;

import java.math.BigInteger;
import java.util.stream.Stream;

public class MersennePrimes {
    public static void main(String[] args) {
        primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE)).filter(mersenne -> mersenne.isProbablePrime(50))
                .limit(20)
                // .forEach(System.out::println);
                .forEach(mp -> System.out.println(mp.bitLength() + ":" + mp));
    }

    static Stream<BigInteger> primes() {
        return Stream.iterate(TWO, BigInteger::nextProbablePrime);
    }
}

Selbst wenn parallel () in der Stream-Verarbeitung dieses Programms enthalten ist, wird die Verarbeitung nicht abgeschlossen und die CPU bleibt weiterhin hoch, anstatt die Verarbeitung zu beschleunigen.

Was passiert hier? Dies liegt einfach daran, dass die Stream-Bibliothek nicht wusste, wie diese Pipeline parallelisiert werden kann, und die heuristische Lösung fehlgeschlagen ist. ** Wenn es ursprünglich von Stream.iterate stammt oder Limit-Zwischenoperationen ausgeführt werden, ist es schwierig, die Leistung durch Parallelisierung der Pipeline zu verbessern. ** Das obige Programm enthält beide Elemente. Die Lehre hier ist, dass ** parallele Streams nicht wahllos verwendet werden **.

Im Allgemeinen kann die ** Parallelisierung die Leistung von Streams mit ArrayList, HashMap, HashSet, ConcurrentHashMap, Arrays, Int-Bereichen und langen Bereichen verbessern. ** ** ** Gemeinsam ist ihnen, dass sie leicht in Unterbereiche unterteilt werden können. Eine weitere wichtige Gemeinsamkeit dieser Datenstrukturen ist die ** Referenzstelle bei sequentieller Verarbeitung. % 85% A7% E3% 81% AE% E5% B1% 80% E6% 89% 80% E6% 80% A7) **.

Kündigungsvorgänge wirken sich auch auf die Leistung der Parallelverarbeitung aus. Wenn eine große Menge an Verarbeitung durch Terminierungsverarbeitung ausgeführt wird und die Terminierungsverarbeitung intern eine sequentielle Verarbeitung im Vergleich zur gesamten Pipeline durchführt, ist die Parallelisierung der Pipeline nicht sehr effektiv. Der effektivste Beendigungsprozess ist der Reduktionsprozess wie min, max, count und sum. Auch [Kurzschlussauswertung] wie anyMatch, allMatch, noneMatch (https://ja.wikipedia.org/wiki/%E7%9F%AD%E7%B5%A1%E8%A9%95%E4%BE%A1) Ist leicht den Effekt der Parallelisierung zu erhalten. Variable Reduktionsoperationen, die mit der Erfassungsmethode des Streams ausgeführt werden, profitieren weniger wahrscheinlich von der Parallelisierung. Dies liegt daran, dass der Verarbeitungsaufwand für das Verbinden von Sammlungen kostspielig ist.

Safety failure ** Die Stream-Parallelisierung verursacht nicht nur Leistungsprobleme, einschließlich eines Lebendigkeitsfehlers, sondern kann auch zu falschen Ergebnissen und unerwartetem Verhalten führen. (Sicherheitsfehler) ** Ein Sicherheitsfehler tritt auf, wenn Sie eine Funktion verwenden, die nicht den strengen Spezifikationen des Streams entspricht. Die zu akkumulierende Funktion und die zu kombinierende Funktion mit der an den Stream übergebenen Funktion lautet beispielsweise kombiniert. In package-summary.html # Associativity), in Non-Interference , Statuslos muss eine Funktion sein. Wenn Sie dies nicht befolgen, verursachen gerade Pipelines keine Probleme, aber parallelisierte Pipelines können katastrophale Folgen haben.

Ist es effektiv zu parallelisieren?

Selbst wenn die Parallelverarbeitung unter sehr guten Bedingungen durchgeführt werden kann, ist sie bedeutungslos, es sei denn, sie zeigt eine Leistung, die die Kosten der Parallelisierung ausgleicht. Eine grobe Schätzung sollte (Anzahl der Elemente im Stream) * (Anzahl der pro Element ausgeführten Codezeilen)> 100000 (wie 10000 bei Betrachtung der Linkquelle) erfüllen. (Http: //gee.cs. oswego.edu / dl / html / StreamParallelGuidance.html)).

Es sollte anerkannt werden, dass die Stream-Parallelisierung eine Leistungsoptimierung ist. Sie müssen sicherstellen, dass jede Optimierung vor und nach der Änderung getestet werden sollte. Im Idealfall sollten Tests in einer Produktionsumgebung durchgeführt werden.

Parallelisierung ist sehr nützlich, wenn sie unter den richtigen Umständen verwendet wird

** Wenn die Parallelisierung unter den richtigen Umständen durchgeführt wird, kann eine Leistungsverbesserung proportional zur Anzahl der Prozessoren erwartet werden. ** ** ** Es ist einfach, diese Leistungen in den Bereichen maschinelles Lernen und Datenverarbeitung zu verbessern.

[Prime Counting-Funktion], die effizient parallelisiert werden kann (https://ja.wikipedia.org/wiki/%E7%B4%A0%E6%95%B0%E8%A8%88%E6%95%B0) Schauen wir uns ein Beispiel für% E9% 96% A2% E6% 95% B0) an.

package tryAny.effectiveJava;

import java.math.BigInteger;
import java.util.stream.LongStream;

public class ParallelTest1 {
    // Prime-counting stream pipeline - benefits from parallelization
    static long pi(long n) {
        return LongStream.rangeClosed(2, n).mapToObj(BigInteger::valueOf).filter(i -> i.isProbablePrime(50)).count();
    }

    public static void main(String[] args) {
        StopWatch sw = new StopWatch();
        sw.start();
        System.out.println(pi(10000000));
        sw.stop();
        System.out.println(sw.getTime());
    }
}

Die Verarbeitung des obigen Codes dauerte ungefähr 42 Sekunden. Parallelisieren Sie dies.

package tryAny.effectiveJava;

import java.math.BigInteger;
import java.util.stream.LongStream;

import org.apache.commons.lang3.time.StopWatch;

public class ParallelTest1 {
    // Prime-counting stream pipeline - benefits from parallelization
    static long pi(long n) {
        return LongStream.rangeClosed(2, n).parallel().mapToObj(BigInteger::valueOf).filter(i -> i.isProbablePrime(50))
                .count();
    }

    public static void main(String[] args) {
        StopWatch sw = new StopWatch();
        sw.start();
        System.out.println(pi(10000000));
        sw.stop();
        System.out.println(sw.getTime());
    }
}

Dies endete in ungefähr 23 Sekunden. (Läuft auf einem 2-Core-Computer)

Beim Parallelisieren eines Stroms zufälliger Werte

Wenn Sie zufällige Werte parallel generieren möchten, sollten Sie SplittableRandom anstelle von ThreadLocalRandom verwenden.

Recommended Posts

Punkt 48: Seien Sie vorsichtig, wenn Sie Streams parallel schalten
Punkt 45: Verwenden Sie Streams mit Bedacht
Stream und paralleler Stream
Punkt 52: Verwenden Sie Überladung mit Bedacht
Punkt 53: Verwenden Sie Varargs mit Bedacht