Seite 1 von 1

Zu schnelles Multiprocessing.

Verfasst: Mittwoch 29. Januar 2020, 03:43
von Spedex
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")
    

Re: Zu schnelles Multiprocessing.

Verfasst: Mittwoch 29. Januar 2020, 04:10
von __deets__
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.

Re: Zu schnelles Multiprocessing.

Verfasst: Mittwoch 29. Januar 2020, 08:07
von Sirius3
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.

Re: Zu schnelles Multiprocessing.

Verfasst: Mittwoch 29. Januar 2020, 09:38
von __blackjack__
@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()

Re: Zu schnelles Multiprocessing.

Verfasst: Donnerstag 30. Januar 2020, 14:16
von DeaD_EyE
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.

Re: Zu schnelles Multiprocessing.

Verfasst: Donnerstag 30. Januar 2020, 14:21
von __deets__
@__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.

Re: Zu schnelles Multiprocessing.

Verfasst: Freitag 31. Januar 2020, 20:59
von Spedex
@__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?

Re: Zu schnelles Multiprocessing.

Verfasst: Freitag 31. Januar 2020, 21:04
von __deets__
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.

Re: Zu schnelles Multiprocessing.

Verfasst: Freitag 31. Januar 2020, 21:14
von Spedex
@__deets__
Aha ok. Danke für die Antwort.

Re: Zu schnelles Multiprocessing.

Verfasst: Samstag 1. Februar 2020, 16:34
von Spedex
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?

Re: Zu schnelles Multiprocessing.

Verfasst: Samstag 1. Februar 2020, 16:44
von __blackjack__
@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.

Re: Zu schnelles Multiprocessing.

Verfasst: Samstag 1. Februar 2020, 20:22
von Spedex
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.

Re: Zu schnelles Multiprocessing.

Verfasst: Sonntag 2. Februar 2020, 14:53
von Spedex
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

Re: Zu schnelles Multiprocessing.

Verfasst: Sonntag 2. Februar 2020, 15:11
von kbr
@Spedex: Die Argumentübergabe muß prozessübergreifend erfolgen.
Das funktioniert etwas anders:

https://docs.python.org/3/library/multi ... cess-class

Re: Zu schnelles Multiprocessing.

Verfasst: Sonntag 2. Februar 2020, 15:37
von Spedex
Alles klar. :)
Danke für deine Antwort.

Re: Zu schnelles Multiprocessing.

Verfasst: Sonntag 2. Februar 2020, 16:14
von __blackjack__
@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``).

Re: Zu schnelles Multiprocessing.

Verfasst: Sonntag 2. Februar 2020, 17:23
von Spedex
:mrgreen: