Java-Parallelität basierend auf der verfügbaren FREIEN CPU

9

FRAGE

Wie skaliere ich, um mehr Threads zu verwenden, wenn und nur wenn es freie CPU gibt? Etwas wie ein ThreadPoolExecutor, der mehr Threads verwendet, wenn CPU-Kerne im Leerlauf sind, und weniger oder nur einen, wenn nicht.

USE CASE

Aktuelle Situation: Meine Java-Server-App verarbeitet Anfragen und liefert Ergebnisse. Es gibt einen ThreadPoolExecutor, der die Anfragen mit einer angemessenen Anzahl von Max-Threads nach folgendem Prinzip abliefert: Anzahl der CPU-Kerne = Anzahl der maximalen Threads. Die geleistete Arbeit ist CPU-lastig und es gibt einige Disk-IO (DBs). Der Code ist linear, single threaded. Eine einzelne Anforderung dauert zwischen 50 und 500 ms. Manchmal gibt es nur ein paar Anfragen pro Minute und manchmal 30 gleichzeitig. Ein moderner Server mit 12 Kernen erledigt die Last gut. Der Durchsatz ist gut, die Latenz ist in Ordnung.

Gewünschte Verbesserung: Wenn es eine geringe Anzahl von Anforderungen gibt, wie es meistens der Fall ist, sind viele CPU-Kerne inaktiv. Die Latenz könnte in diesem Fall verbessert werden, indem ein Teil des Codes für eine einzelne Anfrage mit mehreren Threads ausgeführt wird. Einige Prototypen zeigen Verbesserungen, aber sobald ich mit einer höheren Anzahl gleichzeitiger Anfragen teste, Der Server geht Bananen. Der Durchsatz sinkt, der Speicherverbrauch geht über Bord. 30 gleichzeitige Anfragen, die eine Warteschlange von 10 teilen, was bedeutet, dass maximal 10 laufen können, während 20 warten, und jeder der 10 verwendet bis zu 8 Threads gleichzeitig für Parallelität, scheint zu viel für eine Maschine zu sein mit 12 Kernen (von denen 6 virtuell sind).

Dies scheint mir ein häufiger Anwendungsfall zu sein, aber ich konnte keine Informationen durch Suchen finden.

IDEEN

1) Anfrage zählen Eine Idee ist es, die aktuelle Anzahl der verarbeiteten Anfragen zu zählen. Wenn 1 oder niedrig, dann mehr Parallelität, wenn hoch, dann führe keine aus und führe single-threaded wie vorher weiter. Das klingt einfach zu implementieren. Nachteile sind: Das Zurücksetzen von Request Counters darf keine Fehler enthalten, denke endlich. Und es prüft nicht tatsächlich die verfügbare CPU, vielleicht verwendet ein anderer Prozess auch CPU. In meinem Fall ist die Maschine nur dieser Anwendung gewidmet, aber immer noch.

2) tatsächliche CPU-Abfrage Ich würde denken, dass der richtige Ansatz wäre, einfach die CPU zu fragen und dann zu entscheiden. Seit Java7 gibt es OperatingSystemMXBean.getSystemCpuLoad () siehe Ссылка aber ich kann keine Webseite finden, die getSystemCpuLoad und ThreadPoolExecutor oder ähnliches erwähnt Kombination von Keywords, was mir sagt, dass das kein guter Weg ist. Der JavaDoc sagt "Gibt die" aktuelle CPU-Nutzung "für das ganze System zurück", und ich frage mich was "Aktuelle CPU-Nutzung" bedeutet, wie aktuell das ist und wie teuer dieser Anruf ist.

AKTUALISIEREN

Ich habe diese Frage für eine Weile offen gelassen, um zu sehen, ob mehr Input kommt. Nee. Obwohl ich die "No-can-do" Antwort auf technische Fragen nicht mag, werde ich jetzt Holgers Antwort akzeptieren. Er hat einen guten Ruf, gute Argumente, und andere haben seine Antwort genehmigt. Ich selbst hatte mit Idee 2 etwas experimentiert. Ich habe getSystemCpuLoad () in Tasks abgefragt, um zu entscheiden, wie groß ihr eigener ExecutorService sein könnte. Wie Holger schrieb, können Ressourcen bei einem EINZIGEN ExecutorService gut verwaltet werden. Aber sobald Aufgaben ihre eigenen Aufgaben beginnen, können sie es nicht - es hat für mich nicht funktioniert.

    
Gonfi den Tschal 25.06.2014, 13:00
quelle

2 Antworten

4

Es gibt keine Möglichkeit, basierend auf "freie CPU" zu begrenzen, und es würde sowieso nicht funktionieren. Die Information über "freie CPU" ist veraltet, sobald Sie es bekommen. Angenommen, Sie haben zwölf Threads gleichzeitig ausgeführt und erkennen gleichzeitig, dass es einen freien CPU-Kern gibt und entscheiden, eine Unteraufgabe zu planen ...

Sie können den maximalen Ressourcenverbrauch begrenzen, was sehr gut funktioniert, wenn Sie eine einzige ExecutorService mit einer maximalen Anzahl von Threads für alle Aufgaben verwenden.

Der schwierige Teil ist die Abhängigkeit der Tasks vom Ergebnis der Teilaufgaben, die zu einem späteren Zeitpunkt in die Warteschlange gestellt werden und aufgrund der begrenzten Anzahl von Worker-Threads möglicherweise noch ausstehen.

Dies kann angepasst werden, indem die parallele Ausführung widerrufen wird, wenn die Task feststellt, dass ihre Unteraufgabe noch aussteht. Damit dies funktioniert, erstellen Sie manuell einen FutureTask für die Unteraufgabe und planen Sie ihn mit execute statt mit submit . Fahren Sie dann innerhalb der Aufgabe wie gewohnt fort und prüfen Sie an der Stelle, an der Sie die Unteraufgabe in einer sequentiellen Implementierung durchführen würden, ob Sie remove die FutureTask von ThreadPoolExecutor . Im Gegensatz zu cancel funktioniert das nur, wenn es noch nicht gestartet wurde und daher ein Indikator dafür ist, dass es keine freien Threads gibt. Wenn also remove true zurückgibt, können Sie die Unteraufgabe in-Place ausführen, wobei alle anderen Threads Aufgaben statt Unteraufgaben ausführen. Andernfalls können Sie auf das Ergebnis warten.

An dieser Stelle sollte angemerkt werden, dass es in Ordnung ist, mehr Threads als CPU-Kerne zu haben, wenn die Tasks E / A-Operationen berücksichtigen (oder auf Teilaufgaben warten können). Der wichtige Punkt hier ist haben ein Limit.

%Vor%     
Holger 26.06.2014, 11:58
quelle
0

Es klingt, als wäre die in Java 7 eingeführte ForkJoinPool genau das, was Sie brauchen. Die ForkJoinPool wurde speziell entwickelt, um alle Ihre CPUs genau zu belassen, was bedeutet, dass es so viele Threads wie CPUs gibt und dass alle diese Threads ebenfalls funktionieren und nicht blockieren (Für die spätere Version verwenden Sie ManagedBlocker s für DB-Abfragen).

In ForkJoinTask gibt es die Methode getSurplusQueuedTaskCount , für die der JavaDoc sagt: "Dieser Wert kann für heuristische Entscheidungen darüber nützlich sein, ob andere Tasks forkiert werden sollen." und dient als ein besserer Ersatz für Ihre getSystemCpuLoad -Lösung, um Entscheidungen über Aufgabenzerlegungen zu treffen. Auf diese Weise können Sie die Anzahl der Zerlegungen bei hoher Systemlast reduzieren und somit die Auswirkungen des Task-Zerlegungsaufwands verringern.

Siehe auch meine Antwort hier für eine ausführlichere Erklärung der Prinzipien von Fork / Join-Pools.

>     
yankee 30.05.2016 07:25
quelle