Forum: PC-Programmierung Prozessorlast bei Python


von R. F. (inet_surfer88)


Lesenswert?

Hallo,

ich habe folgendes Programm für Python geschrieben.
1
import time
2
3
anfang=time.time()
4
zeitdifferenz=0
5
counter=0
6
7
while counter<=600:
8
  
9
  "Unterprogrammaufruf"
10
11
  while zeitdifferenz<=1:
12
    ende=time.time()
13
    zeitdifferenz=ende-anfang
14
15
  anfang=time.time()
16
  zeitdifferenz=0
17
  counter+=1

Sinn des ganzen:
Es wird 10 Minuten lang 1x in der Sekunde ein Unterprogramm aufgerufen. 
Das Unterprogramm läuft nur wenige Millisekunden. Wenn das Programm 
ausgeführt wird, geht die Prozessorlast auf nahezu 100%, und das für die 
gesamte Laufzeit von 10 Minuten. Gibt es eine bessere Möglichkeit, die 
Wartezeit von 1 Sekunde zu erreichen?
1
time.sleep(1)
möchte ich nicht unbedigt verwenden, da das Unterprogramm nicht immer 
die gleiche Laufzeit benötigt, und es so zu Ungenauigkeiten kommt.

Kann man das ganze z.B. mit Interrupts lösen? Bisher habe ich hie 
rnichts passendes gefunden. Alle vorschläge gingen in die Richtung von 
time.sleep.


Gruß Rüdiger

von Peter II (Gast)


Lesenswert?

rechne doch einfach die zeit aus, wenn das nächste mal die Funktion 
aufgerufen werden muss. Dann wartest du mit sleep die Differenz ab.

von R. F. (inet_surfer88)


Lesenswert?

Hallo,

danke für die schnelle Antwort.
Ich habe es jetzt folgendermasen probiert:
1
import time
2
counter=0
3
4
while counter<=600:
5
6
  counter+=1
7
  
8
  anfang=time.time()
9
10
  "Unterprogrammaufruf"
11
12
  ende=time.time()
13
  zeit=ende-anfang
14
15
  time.sleep(1-zeit)

Das sieht schon sehr viel besser aus. Die Prozessorlast ist schon 
relativ gering.
Ich warte mal noch ab, ob noch weitere Vorschläge kommen. Ansonsten ist 
das schon mal ein möglicher Weg. Dieser Weg hat noch eine geringe 
Ungenauigkeit in der Zeit, damit könnte ich aber noch leben. Mein erster 
Versuch war schließlich auch nicht zu 100% genau.


Gruß Rüdiger

von T.roll (Gast)


Lesenswert?

Wie immer bei so ungenauen Fragen:

Was soll das Programm denn nun wirklich machen. Wahrscheinlich gibt es 
viel bessere Lösung als Zeitschleifen.

von Rolf M. (rmagnus)


Lesenswert?

R. F. schrieb:
> Es wird 10 Minuten lang 1x in der Sekunde ein Unterprogramm aufgerufen.
> Das Unterprogramm läuft nur wenige Millisekunden. Wenn das Programm
> ausgeführt wird, geht die Prozessorlast auf nahezu 100%, und das für die
> gesamte Laufzeit von 10 Minuten. Gibt es eine bessere Möglichkeit, die
> Wartezeit von 1 Sekunde zu erreichen?

Das hat zunächst nichts mit Python zu tun. Wäre in jeder anderen Sprache 
auch so. Dein Programm prüft in einer Schleife so schnell wie der 
Rechner kann (also vermutlich einige Millionen mal pro Sekunde), ob die 
Zeit denn schon abgelaufen ist. Das heißt natürlich, dass es den 
Prozessor auch ständig zu 100% mit diesen Massen an Vergleichen 
auslastet.
Solche Schleifen sind daher in Multitasking-Systemen zu vermeiden. 
Besser ist, den Prozess so lange schlafen zu legen, bis es wieder etwas 
zu tun gibt. Dadurch berücksichtigt ihn das Betriebssystem (konkret der 
Scheduler) in der Zeit bis dahin nicht, und er verbraucht keine unnötige 
Rechenzeit. Das entspricht dem Vorschlag von Peter II.

R. F. schrieb:
> Dieser Weg hat noch eine geringe Ungenauigkeit in der Zeit, damit könnte
> ich aber noch leben.

Es gibt auch Funktionen, bei denen man die Wartezeit nicht relativ, 
sondern absolut angibt, also nicht, wie lange man warten will, sondern 
bis wann. Damit bekommt man die Ungenauigkeit weg. Ob das mit Python 
portabel geht und wenn ja, wie, weiß ich aber nicht.

: Bearbeitet durch User
von Florian F. (flof3000)


Lesenswert?

Mit twisted oder einem beliebigen anderen Event-Reactor trivial:

1
from twisted.internet import task
2
from twisted.internet import reactor
3
4
def runEverySecond():
5
    print "a second has passed"
6
7
l = task.LoopingCall(runEverySecond)
8
l.start(1.0) # call every second
9
10
# l.stop() will stop the looping calls
11
reactor.run()

von R. F. (inet_surfer88)


Angehängte Dateien:

Lesenswert?

Hallo,

wie gewünscht mehr Infos zur Aufgabenstellung.
Ich baue eine Beleuchtungssteuerung für eine Modellbahn, bei welcher 
jede Lampe einzeln über den Rechner gesteuert werden kann.
Die Lampen werden von einer Zentrale über den Selectrix-Bus 
(Modellbahnspezifisches Bussystem) gesteuert. Die Zentrale erhält ihre 
Befehle vom Rechner über eine RS232-Schnittstelle und setzt die Befehle 
in das Selecrtrix-System um. Ich muss nur dann neue Befehle senden, wenn 
ich einen Schaltzustand ändern möchte. Kommt über die RS232 kein neuer 
Befehl, behält die Zentrale den aktuellen Schaltzustand bei.

Mein Python-Programm besteht im wesentlichen aus 3 Teilen:
1. Dem oben stehenden Hauptprogramm. Dieses ruft 1 mal in der Sekunde 
die Unterprogramme auf und erzeugt die Zählvariable counter.

2. Ein Unterprogramm für Jedes Haus. Pro Lampe gibt es einen 
True/False-Wert. In Abhängigkeit des Counterwertes werden die einzelnen 
Lampen auf True oder False gesetzt. Pro Haus werden die einzelnen Werte 
in einer Liste zusammengefasst und per return an das Hautprogramm 
übergeben.

3. Ein Unterprogramm pro Adresse. Eine Adresse besteht aus 8 Bit und 
kann somit 8 Lampen steuern. Das Unterprogramm bekommt die Liste von dem 
zugeordneten Haus oder mehreren Häusern übergeben und prüft, ob es eine 
Veränderung gab. Wenn nein, passiert nichts. Wenn ja, wird der Wert über 
die RS232 gesendet.

Warum fasse ich die Unterprogramme für das Haus und die Adresse nicht 
zusammen und spare mir den Umweg über die Listen?
Das hat folgenden Grund. Die Zentrale erwartet pro Adresse immer genau 8 
Bit. Jedes Haus hat aber eine andere Anzahl an Lampen. Dadurch haben 
viele Häuser mehrere Adressen. weiterhin kommt es vor, dass einige 
Adressen Lampen von 2 unterschiedlichen Häusern ansteuern. Dadurch kann 
ich alle möglichen Bits effektiv ausnutzen.
Wegen der Übersichtlichkeit bei späteren Änderungen will ich aber pro 
Haus eine Datei haben. Deswegen diese Trennung und damit der Umweg üner 
die Listen (Arrays).

Eine Anmerkung noch zu dem RS232-Protokoll. Die Zentrale erwartet pro 
Adresse 2 Byte. Das erste Byte ist die Adresse (Bit 0 bis 6) und ein 
Schteib/Lese-Bit an Bit7. Das zweite Byte sind die Daten für die 8 
Schaltkanäle.


Gruß Rüdiger

PS: Im Anhang das Programm. Fertig ist es allerding snoch nicht. Da 
fehlen noch unzählige Häuser.

von Yalu X. (yalu) (Moderator)


Lesenswert?

So kann man das Driften der Aufrufzeitpunkte vermeiden, falls es stören
sollte:

1
from time import time, sleep
2
3
interval = 1.0
4
5
def func():
6
  # Log absolute and relative times
7
  global last
8
  t = time()
9
  print('called at %.7fs' % t, end='')
10
  if last != None:
11
    print(', interval = %.7fs, error = %+7.1fµs' %
12
          (t - last, 1e6 * (t - last - interval)))
13
  else:
14
    print()
15
  last = t
16
17
  # actual function code
18
  # ...
19
20
21
t = time();
22
last = None
23
for counter in range(601):
24
  t += interval
25
  dt = t - time()
26
  if dt > 0:
27
    sleep(dt)
28
  func()

Es verbleibt zwar ein unvermeidbarer Jitter, jedoch sind die
Zeitintervalle im Mittel exakt 1s lang (bezogen auf die Systemuhr):

1
called at 1504706796.0261014s
2
called at 1504706797.0261033s, interval = 1.0000019s, error =    +1.9µs
3
called at 1504706798.0261016s, interval = 0.9999983s, error =    -1.7µs
4
called at 1504706799.0261021s, interval = 1.0000005s, error =    +0.5µs
5
called at 1504706800.0260313s, interval = 0.9999292s, error =   -70.8µs
6
called at 1504706801.0261009s, interval = 1.0000696s, error =   +69.6µs
7
called at 1504706802.0261173s, interval = 1.0000165s, error =   +16.5µs
8
called at 1504706803.0260749s, interval = 0.9999576s, error =   -42.4µs
9
called at 1504706804.0260518s, interval = 0.9999769s, error =   -23.1µs
10
called at 1504706805.0260921s, interval = 1.0000403s, error =   +40.3µs
11
called at 1504706806.0260987s, interval = 1.0000067s, error =    +6.7µs
12
called at 1504706807.0255022s, interval = 0.9994035s, error =  -596.5µs
13
called at 1504706808.0261014s, interval = 1.0005991s, error =  +599.1µs
14
called at 1504706809.0260994s, interval = 0.9999981s, error =    -1.9µs
15
called at 1504706810.0261006s, interval = 1.0000012s, error =    +1.2µs
16
called at 1504706811.0260971s, interval = 0.9999964s, error =    -3.6µs
17
called at 1504706812.0261002s, interval = 1.0000031s, error =    +3.1µs
18
called at 1504706813.0260997s, interval = 0.9999995s, error =    -0.5µs

von R. F. (inet_surfer88)


Lesenswert?

Hallo,

zunächst einmal Danke an alle für eure Mühe, echt klasse!

Der aktuelle Stand ist derzeit folgender:

Ich habe das Programm jetzt auf meinen RaspberryPi kopiert und getestet. 
Die zweite von mir gepostete Variante hat eine Abweichung von unter 2 
Sekunden auf die gesamten 10 Minuten. Das reicht vollkommen aus, das 
merkt bei der Anwendung kein Mensch. Die Prozessorlast ist fast nicht 
messbar. Wenn der Pi sonst nichts zu tun hat, liegt die Anzeige bei ca. 
1%. Perfekt!

Auf dieser Basis werde ich das Programm erst einmal weiterentwickeln. 
Alle anderen geposteten Vorschläge werde ich bei Gelegenheit 
ausprobieren bzw. mich einlesen. Für dieses Projekt zwar vorerst nicht 
mehr relevant, aber man will ja dazu lernen. Und vielleicht kann man das 
erlernte dann im nächsten Projekt gebrauchen, oder irgend wann dieses 
Programm verbessern. Falls es noch weitere Varianten gibt, immer her 
damit. Das nächste verregnete Wochenende kommt bestimmt!

Das gleiche gilt auch für andere Fehler oder Verbesserungen, welche ihr 
findet. Ich bin für alle Anregungen und Tipps dankbar, auch wenn es 
jetzt erst einmal funktioniert.


Gruß Rüdiger

von Eric B. (beric)


Lesenswert?

R. F. schrieb:
> Warum fasse ich die Unterprogramme für das Haus und die Adresse nicht
> zusammen und spare mir den Umweg über die Listen?

Weil das, so wie du es jetzt machst einfach Guter Programmierstil[TM] 
ist :-) Lob am Freitag dafür!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.