webentwicklung-frage-antwort-db.com.de

Verwendung von 100% aller Kerne mit dem Multiprocessing-Modul

Ich habe zwei Teile des Codes, mit denen ich mehr über Multiprocessing in Python 3.1 lerne. Mein Ziel ist es, 100% aller verfügbaren Prozessoren zu verwenden. Die Code-Schnipsel erreichen hier jedoch nur 30% bis 50% aller Prozessoren.

Gibt es trotzdem einen Python 'zu zwingen', alle 100% zu verwenden? Beschränkt das Betriebssystem (Windows 7, 64bit) den Zugriff von Python auf die Prozessoren? Während die folgenden Codeausschnitte ausgeführt werden, öffne ich den Task-Manager und Beobachten Sie die Prozessor-Spitze, aber erreichen und erhalten Sie niemals 100% . Außerdem kann ich mehrere python.exe-Prozesse sehen, die auf dem Weg erstellt und zerstört wurden. Wie hängen diese Prozesse mit den Prozessoren zusammen? Wenn ich beispielsweise 4 Prozesse spawne, verwendet jeder Prozess nicht seinen eigenen Kern. Was verwenden stattdessen die Prozesse? Teilen sie alle Kerne? Und wenn ja, zwingt das Betriebssystem die Prozesse dazu, die Kerne gemeinsam zu nutzen?

code-Ausschnitt 1

import multiprocessing

def worker():
    #worker function
    print ('Worker')
    x = 0
    while x < 1000:
        print(x)
        x += 1
    return

if __== '__main__':
    jobs = []
    for i in range(50):
        p = multiprocessing.Process(target=worker)
        jobs.append(p)
        p.start()

codeausschnitt 2

from multiprocessing import Process, Lock

def f(l, i):
    l.acquire()
    print('worker ', i)
    x = 0
    while x < 1000:
        print(x)
        x += 1
    l.release()

if __== '__main__': 
    lock = Lock()
    for num in range(50):
        Process(target=f, args=(lock, num)).start()
36
Ggggggg

Um 100% aller Kerne zu verwenden, erstellen und zerstören Sie keine neuen Prozesse.

Erstellen Sie einige Prozesse pro Kern und verknüpfen Sie diese mit einer Pipeline.

Auf Betriebssystemebene laufen alle Pipeline-Prozesse gleichzeitig.

Je weniger Sie schreiben (und je mehr Sie an das Betriebssystem delegieren), desto wahrscheinlicher ist es, dass Sie so viele Ressourcen wie möglich verwenden.

python p1.py | python p2.py | python p3.py | python p4.py ...

Wird die CPU maximal ausnutzen.

35
S.Lott

Sie können psutil verwenden, um jeden Prozess anhängen , erstellt von multiprocessing für eine bestimmte CPU:

import multiprocessing as mp
import psutil


def spawn():
    procs = list()
    n_cpus = psutil.cpu_count()
    for cpu in range(n_cpus):
        affinity = [cpu]
        d = dict(affinity=affinity)
        p = mp.Process(target=run_child, kwargs=d)
        p.start()
        procs.append(p)
    for p in procs:
        p.join()
        print('joined')

def run_child(affinity):
    proc = psutil.Process()  # get self pid
    print('PID: {pid}'.format(pid=proc.pid))
    aff = proc.cpu_affinity()
    print('Affinity before: {aff}'.format(aff=aff))
    proc.cpu_affinity(affinity)
    aff = proc.cpu_affinity()
    print('Affinity after: {aff}'.format(aff=aff))


if __== '__main__':
    spawn()

Hinweis: Wie bereits erwähnt, ist psutil.Process.cpu_affinity unter macOS nicht verfügbar.

11

Bezüglich Code-Snippet 1: Wie viele Kerne/Prozessoren haben Sie auf Ihrer Testmaschine? Es ist nicht gut, 50 dieser Prozesse auszuführen, wenn Sie nur zwei CPU-Kerne haben. In der Tat zwingen Sie das Betriebssystem dazu, mehr Zeit für den Kontextwechsel zu verwenden, um Prozesse auf die CPU zu verschieben und aus ihr zu entfernen, als die eigentliche Arbeit.

Versuchen Sie, die Anzahl der erzeugten Prozesse auf die Anzahl der Kerne zu reduzieren. "For i in range (50):" sollte also wie folgt aussehen:

import os;
# assuming you're on windows:
for i in range(int(os.environ["NUMBER_OF_PROCESSORS"])):
    ...

Was Code-Snippet 2 betrifft: Sie verwenden ein Multiprocessing.Lock, das jeweils nur von einem Prozess gehalten werden kann, sodass Sie die Parallelität in dieser Programmversion vollständig einschränken. Sie haben die Dinge serialisiert, so dass die Prozesse 1 bis 50 beginnen, ein zufälliger Prozess (z. B. Prozess 7) die Sperre erlangt. Die Prozesse 1-6 und 8-50 befinden sich alle auf der Linie:

l.acquire()

Während sie dort sitzen, warten sie nur auf die Freigabe der Sperre. Abhängig von der Implementierung des Lock-Grundelements verwenden sie wahrscheinlich keine CPU. Sie sitzen lediglich mit Systemressourcen wie RAM dort, erledigen jedoch keine sinnvolle Arbeit mit der CPU. Prozess 7 zählt und druckt bis 1000 und gibt dann die Sperre frei. Das Betriebssystem kann dann zufällig einen der verbleibenden 49 Prozesse für die Ausführung planen. Derjenige, der zuerst aufwacht, erhält die Sperre als Nächstes und läuft, während die restlichen 48 auf die Sperre warten. Dies wird für das gesamte Programm fortgesetzt. 

Grundsätzlich ist Code-Snippet 2 ein Beispiel dafür, was Parallelität schwierig macht. Sie müssen den Zugriff vieler Prozesse oder Threads auf einige freigegebene Ressourcen verwalten. In diesem speziellen Fall gibt es wirklich keinen Grund, dass diese Prozesse aufeinander warten müssen. 

Von diesen beiden ist Snippet 1 also eher der effizienteren Nutzung der CPU. Ich denke, die richtige Anpassung der Anzahl der Prozesse an die Anzahl der Kerne führt zu einem viel besseren Ergebnis. 

6
stderr

Minimales Beispiel in Python:

def f(x):
    while 1:
        pass  # infinite loop

import multiprocessing as mp
n_cores = mp.cpu_count()
with mp.Pool(n_cores) as p:
    p.map(f, range(n_cores))

Verbrauch: um sich an einem kalten Tag aufzuwärmen (aber ändern Sie den Loop auf etwas weniger Sinnloses.)

Warnung: Zum Beenden nicht den Stecker ziehen oder den Netzschalter gedrückt halten, stattdessen Strg-C.

2
THN