Zu schnelles Multiprocessing.

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Hey,
ich habe in Python ein Programm geschrieben:
Download:
https://we.tl/t-AJ1Cuhw7gE
Dort habe ich 16 Funktionen erstellt, die alle 1 Sekunde "sleepen" - sprich warten.
Diese 16 Funktionen möchte ich jetzt mit multi-processing ausführen.
Da mein PC allerdings keine 16 Kerne hat, hätte ich mir erwartet, dass der Vorgang länger als 2 Sekunden dauert. Sprich zuerste arbeiten die Kerne möglichst viele Funktionen ab, und wenn diese abgearbeitet sind, kommt der Rest, was dann etwas über 2 Sekunden sein sollte.
Allerdings stellt sicher heraus, dass das Ganze nur etwas über 1 Sekunde gedauert hat. Sprich es wurden alles 16 Funktionen gleichzeitig abgearbeitet.
Könnt ihr euch bzw. mir erklären, warum das so ist?
LG Spedex

Code:

Code: Alles auswählen

import multiprocessing
import time

def kern1():
    print("Kern 1 Startet")
    time.sleep(1)
    print("Kern 1 Ende")

def kern2():
    print("Kern 2 Startet")
    time.sleep(1)
    print("Kern 2 Ende")

def kern3():
    print("Kern 3 Startet")
    time.sleep(1)
    print("Kern 3 Ende")

def kern4():
    print("Kern 4 Startet")
    time.sleep(1)
    print("Kern 4 Ende")

def kern5():
    print("Kern 5 Startet")
    time.sleep(1)
    print("Kern 5 Ende")

def kern6():
    print("Kern 6 Startet")
    time.sleep(1)
    print("Kern 6 Ende")

def kern7():
    print("Kern 7 Startet")
    time.sleep(1)
    print("Kern 7 Ende")

def kern8():
    print("Kern 8 Startet")
    time.sleep(1)
    print("Kern 8 Ende")

def kern9():
    print("Kern 9 Startet")
    time.sleep(1)
    print("Kern 9 Ende")

def kern10():
    print("Kern 10 Startet")
    time.sleep(1)
    print("Kern 10 Ende")

def kern11():
    print("Kern 11 Startet")
    time.sleep(1)
    print("Kern 11 Ende")

def kern12():
    print("Kern 12 Startet")
    time.sleep(1)
    print("Kern 12 Ende")

def kern13():
    print("Kern 13 Startet")
    time.sleep(1)
    print("Kern 13 Ende")

def kern14():
    print("Kern 14 Startet")
    time.sleep(1)
    print("Kern 14 Ende")

def kern15():
    print("Kern 15 Startet")
    time.sleep(1)
    print("Kern 15 Ende")

def kern16():
    print("Kern 16 Startet")
    time.sleep(1)
    print("Kern 16 Ende")

if __name__ == "__main__":
    p1 = multiprocessing.Process(target=kern1)
    p2 = multiprocessing.Process(target=kern2)
    p3 = multiprocessing.Process(target=kern3)
    p4 = multiprocessing.Process(target=kern4)
    p5 = multiprocessing.Process(target=kern5)
    p6 = multiprocessing.Process(target=kern6)
    p7 = multiprocessing.Process(target=kern7)
    p8 = multiprocessing.Process(target=kern8)
    p9 = multiprocessing.Process(target=kern9)
    p10 = multiprocessing.Process(target=kern10)
    p11 = multiprocessing.Process(target=kern11)
    p12 = multiprocessing.Process(target=kern12)
    p13 = multiprocessing.Process(target=kern13)
    p14 = multiprocessing.Process(target=kern14)
    p15 = multiprocessing.Process(target=kern15)
    p16 = multiprocessing.Process(target=kern16)
    start_time = time.time()
    p1.start()
    p2.start()
    p3.start()
    p4.start()
    p5.start()
    p6.start()
    p7.start()
    p8.start()
    p9.start()
    p10.start()
    p11.start()
    p12.start()
    p13.start()
    p14.start()
    p15.start()
    p16.start()
    p1.join()
    p2.join()
    p3.join()
    p4.join()
    p5.join()
    p6.join()
    p7.join()
    p8.join()
    p9.join()
    p10.join()
    p11.join()
    p12.join()
    p13.join()
    p14.join()
    p15.join()
    p16.join()
    end_time = time.time()
    time_diff = end_time - start_time
    print(f"Benötigte Zeit beträgt {time_diff} Sekunden")
    
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst dringend mal etwas über Listen und Parameterübergabe an Funktionen lernen - das zahlt sich aus, da schnurrt dein Code auf einen Bruchteil zusammen und ist auch noch flexibler. Und der nächste Schritt wäre ein Executor Pool, statt alle Prozesse selbst zu starten und auf die zu warten.

Zu deiner Frage: sleep arbeitet nicht. Sleep sagt dem Kernel „ich will schlafen, kannst wen anderes dran lassen“. Und das macht der dann auch.

Wenn du das von dir gewünschte Verhalten willst, musst du schon etwas tun, das eine Sekunde dauert. Zb eine busy loop die entsprechend prüft, ob schon die gewünschte Elektro vergangen ist.
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Und dem Executor-Pool kann man dann auch einfach sagen, dass er nicht 16 Prozesse gleichzeitig abarbeiten soll, sondern nur so viele, wie es Kerne gibt, und dann dauert das nichts-tun auch 2 Sekunden.
Benutzeravatar
__blackjack__
User
Beiträge: 14051
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Spedex: Noch ein paar Anmerkungen: Das was im ``if __name__ …``-Zweig steht, sollte in einer Funktion stecken, denn so wie es jetzt ist, werden Variablen auf Modulebene definiert die dort nicht hingehören.

Anstelle von `time()` sollte man hier `monotonic()` verwenden.

Selbst „busy waiting“ bringt bei mir nicht wirklich etwas.

Code: Alles auswählen

#!/usr/bin/env python3
import multiprocessing
import time


def kern(number):
    print(f"Kern {number} Startet")
    start_time = time.monotonic()
    while time.monotonic() - start_time < 1:
        pass
    print(f"Kern {number} Ende")


def main():
    processes = [
        multiprocessing.Process(target=kern, args=[i + 1]) for i in range(16)
    ]
    start_time = time.monotonic()
    for process in processes:
        process.start()
    for process in processes:
        process.join()
    end_time = time.monotonic()
    time_diff = end_time - start_time
    print(f"Benötigte Zeit beträgt {time_diff} Sekunden")


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
DeaD_EyE
User
Beiträge: 1239
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Mit der Systemzeit können komische Sachen passieren:
- Korrektur der Systemzeit durch ntp
- Drift der Systemzeit durch ntp
- Manuelles setzen der Systemzeit

Dann hat man einen Sprung in der Zeit.
Bei time.time kann das passieren, aufgrund der Abhängigkeit zur Systemzeit.
time.monotonic läuft aber immer vorwärts und niemals rückwärts, da sich das auf die Zyklen des Prozessors bezieht.

Meistens fallen solche Fehler sehr selten auf und wenn sie auftreten, kann die Fehlersuche länger dauern.
Ich meine mal gelesen zu haben, dass sogar das threading modul davon betroffen gewesen sein sollte.
Wo dieser Fehler definitiv häufiger auftreten wird, ist z.B. auf einem Raspberry Pi, da der keine Pufferbatterie für die Uhrzeit hat und die Systemzeit durch ntp korrigiert wird.
Auf einem Desktop-PC mit Pufferbatterie tritt das wahrscheinlich nach dem Aufwachen aus dem Standby-Modus oder Ruhezustand auf.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

@__blackjack__: ich würde es mal mit einem “echten” busy wait probieren. Zb eine große Potenz erstellen. Ich habe zwar auch keine wirkliche schlüssige Erklärung, aber du setzt die ganze Zeit nur einen systemcall ab. Und da bin ich mir nicht sicher, ob der scheduler das nicht auch zum Anlass nimmt, mal woanders zu werkeln.
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

@__blackjack__
Wo liegt denn der Unterschied zwischen time.time() und time.monotonic()?
Ich hab mal auf https://docs.python.org/3/library/time.html#time.time nachgeschaut.
Da steht bei time.monotonic() unter anderem folgendes:

Return the value (in fractional seconds) of a monotonic clock, i.e. a clock that cannot go backwards. The clock is not affected by system clock updates. The reference point of the returned value is undefined, so that only the difference between the results of consecutive calls is valid.

Liegt der Vorteil also dabei, dass time.monotonic() nicht von Hardware-Einflussgrößen abhängig ist bzw. von Hardware-Einflussgrößen gestört wird und deswegen genauer ist?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein. Wie liest du das aus der Beschreibung? Da steht doch nichts von hardware oder Stoerungen.

Die System-Zeit kann sich jederzeit aendern. ZB wenn von Sommer- auf Winterzeit gewechselt wird. Dann wartest du im besten Fall statt 3601 Sekunden, statt einer. Im schlimmsten stuerzt dein Programm ab. time.monotonic hat dieses Problem nicht, fuer das Messen von verflossener Zeit ist das also ideal.

Edit Das hat auch Dead_Eye schon ausgefuehrt.
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

@__deets__
Aha ok. Danke für die Antwort.
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Beim Ausführen des Programms, welches von @__blackjack__ gepostet wurde, erscheint bei mir folgendes Fehlermeldung:

Code: Alles auswählen

  File "c:/Users/xx/Documents/Python/multiprocessing.py", line 25, in <module>
    main()
  File "c:/Users/xx/Documents/Python/multiprocessing.py", line 13, in main
    processes = [multiprocessing.Process(target=kern, args=[i + 1]) for i in range(16)]
  File "c:/Users/xx/Documents/Python/multiprocessing.py", line 13, in <listcomp>
    processes = [multiprocessing.Process(target=kern, args=[i + 1]) for i in range(16)]
AttributeError: module 'multiprocessing' has no attribute 'Process'
Hier nochmal der Code:

Code: Alles auswählen

import multiprocessing
import time

def kern(number):
    print(f"Kern {number} Startet")
    start_time = time.monotonic()
    while time.monotonic() - start_time < 1:
        pass
    print(f"Kern {number} Ende")


def main():
    processes = [multiprocessing.Process(target=kern, args=[i + 1]) for i in range(16)]
    start_time = time.monotonic()
    for process in processes:
        process.start()
    for process in processes:
        process.join()
    end_time = time.monotonic()
    time_diff = end_time - start_time
    print(f"Benötigte Zeit beträgt {time_diff} Sekunden")


if __name__ == "__main__":
    main()
Modul multiprocressing soll wohl angeblich kein Attribut Process haben.
Dabei wird mir Process dank IntelliSense sogar schon vorgeschlagen und auch bei meinem Programm funktioniert es einwandfrei.
Woran kann das liegen?
Benutzeravatar
__blackjack__
User
Beiträge: 14051
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Spedex: Das liegt daran, dass es in dem Modul mit dem Namen `multiprocessing` das *Du* da gerade angelegt hast in dem Du Dein Modul "multiprocessing.py" genannt hast, in der Tat kein Attribut `Process` gibt.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Ah ok, alles klar. Hab den Namen geändert und jetzt funktioniert es.
Daran habe ich gar nicht gedacht :)
Vielen Dank für deine Antwort.
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Noch eine Frage zur Syntax in der Funktion main(), welche von @__blackjack__ gepostet wurde:
Hier die Funktion:

Code: Alles auswählen

def main():
    processes = [multiprocessing.Process(target=kern, args=[i + 1]) for i in range(12)]
    start_time = time.monotonic()
    for process in processes:
        process.start()
    for process in processes:
        process.join()
    end_time = time.monotonic()
    time_diff = end_time - start_time
    print(f"Benötigte Zeit beträgt {time_diff} Sekunden")
Es geht um diese Zeile:

Code: Alles auswählen

processes = [multiprocessing.Process(target=kern, args=[i + 1]) for i in range(12)]
Wie wird hier denn erreicht, dass der Funktion kern(number), die Zahl 1 bis 12 mitgegeben wird.
Ich habe test-weise folgendes Programm geschrieben:

Code: Alles auswählen

def printnum(number):
    print(f"Die Nummer lautet {str(number)}")

def main():
    for number in range(10):
        printnum(number)

if __name__ == "__main__":
    main()
Hier ist mir klar, wie der Funktion printnum(number) der Wert number in der Funktion main() mitgegeben wird.
Doch der Aufbau schaut so nicht in der multiprocessing Zeile aus.
Die Variable args wird im Programm nur in dieser Zeile verwendet, sonst nicht. Dient args als Ersatz für den Klammerterm nach einer Funktion und wieso wird das in die gleiche Zeile geschrieben?
Ich hoffe ihr könnt mir da weiterhelfen.
LG Spedex
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Spedex: Die Argumentübergabe muß prozessübergreifend erfolgen.
Das funktioniert etwas anders:

https://docs.python.org/3/library/multi ... cess-class
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

Alles klar. :)
Danke für deine Antwort.
Benutzeravatar
__blackjack__
User
Beiträge: 14051
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Spedex: `args` ist keine Variablen sondern ein Argument von `Process`. Man ruft `kern()` da ja nicht selbst in der `main()`-Funktion auf sondern man erstellt ein `Process`-Objekt und gibt dem die Information mit was aufgerufen werden soll (`kern()`) und mit welchen Argumenten (ein Argument mit dem Wert ``i + 1``).
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Spedex
User
Beiträge: 54
Registriert: Mittwoch 29. Januar 2020, 03:27

:mrgreen:
Antworten