PyPy: hat jemand Erfahrung?

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Micki
User
Beiträge: 34
Registriert: Freitag 20. März 2020, 09:17

Hallo Forum,
ich möchte einen Keyboard-Controller bauen. Das Programm ist fertig, reagiert aber ein wenig zu langsam.
Jetzt habe ich gelesen, dass man mit PyPy eine Steigerung der Geschwindigkeit um das 5fache hinbekommen soll. Also hab ich das probiert, bin aber gescheitert, weil ich das System nicht verstanden habe. Hat von Euch jemand Erfahrungen mit PyPy?

Ich habe das Programm hier mal beigefügt. Vielleicht fällt auf, dass zur Vermeidung von Tastenprellern und Mehrfachaufrufen regelmäßig "time.sleep(0.5)" eingefügt ist. Das kostet natürlich Zeit. Hat jemand eine Idee, wie man Tastenpreller anders vermeiden kann? Vielleicht durch eine andere Routine die sagt "ja, ok, ich habe verstanden, dass ein Knopf gedrückt wurde."

Code: Alles auswählen

import RPi.GPIO as GPIO
import time
import mido

GPIO.setmode(GPIO.BCM)
GPIO.setup(5, GPIO.IN)  # knob 1
GPIO.setup(22, GPIO.IN) # knob 2
GPIO.setup(27, GPIO.IN) # knob 3
GPIO.setup(17, GPIO.IN) # knob 4
GPIO.setup(4, GPIO.IN)  # knob 5
GPIO.setup(12, GPIO.IN) # knob 6
GPIO.setup(25, GPIO.IN) # knob 7
GPIO.setup(24, GPIO.IN) # knob 8
GPIO.setup(23, GPIO.IN) # knob 9
GPIO.setup(18, GPIO.IN) # knob 10
pc = 1
ch = 0
ton = True

# ----------------------------------------- 
def PC():        # Prog Change senden 
    global pc
    global ch
    global ton
    with mido.open_output("USB MIDI Interface MIDI 1") as outport:
        outport.send(mido.Message('control_change',
            channel=ch, control=0, value=0))    #   MSB
        outport.send(mido.Message('control_change',
            channel=ch, control=32, value=0))  #    LSB
        outport.send(mido.Message('program_change',
            channel=ch, program=pc)) #    PC   
        print('CH =', ch,'   ', 'PC = ', pc)
        PerCon()
# -----------------------------------------       
def PerCon():
    global pc
    global ch
    global ton
    while True:  
# Song start         
        if GPIO.input(5) == 0:    
            with mido.open_output("USB MIDI Interface MIDI 1") as outport:
                time.sleep(0.5)
                outport.send(mido.Message('note_on',        
                channel=15, note=27, velocity=65))        
                print('Song Start')
                
# Song Stopp                   
        if GPIO.input(22) == 0:      
            with mido.open_output("USB MIDI Interface MIDI 1") as outport:
                time.sleep(0.5)
                outport.send(mido.Message('note_on',        
                channel=15, note=28, velocity=65))        
                print('Song Stopp')
                
# PC = 1       
        if GPIO.input(27) == 0:
            time.sleep(0.5)
            ch = 0
            pc = 1
            PC()        
# PC = 2       
        if GPIO.input(17) == 0:
            time.sleep(0.5)
            ch = 0
            pc = 2
            PC()        
# PC = 3       
        if GPIO.input(4) == 0:
            time.sleep(0.5)
            ch = 0
            pc = 3
            PC()        
# PC = 4       
        if GPIO.input(12) == 0:
            time.sleep(0.5)
            ch = 0
            pc = 4
            PC()        
# PC = 5       
        if GPIO.input(25) == 0:
            time.sleep(0.5)
            ch = 0
            pc = 5
            PC()        
# PC = 6       
        if GPIO.input(24) == 0:
            time.sleep(0.5)
            ch = 0
            pc = 6
            PC()        
# PC = 7       
        if GPIO.input(23) == 0:
            with mido.open_output("USB MIDI Interface MIDI 1") as outport:
                time.sleep(0.5)
                outport.send(mido.Message('note_on',        
                channel=15, note=9, velocity=65))
                print('All Notes Off')     
# PC = 8       
        if GPIO.input(18) == 0:
            global ton
            time.sleep(0.5) 
            if ton == True:
                with mido.open_output("USB MIDI Interface MIDI 1") as outport:
                    outport.send(mido.Message('control_change',        
                    channel=0, control=64, value=127))   
                print('Damper on')
                ton = not ton
            else:
                with mido.open_output("USB MIDI Interface MIDI 1") as outport:
                    outport.send(mido.Message('control_change',        
                    channel=0, control=64, value=0))   
                print('Damper off')
                ton = not ton  
# ---------------------------------------------------------
PerCon()
nezzcarth
User
Beiträge: 1736
Registriert: Samstag 16. April 2011, 12:47

PyPy hilft dir hier eher nicht weiter. PyPy kann zum Beispiel manchen Code deswegen schneller ausführen, weil es unter anderem zur Laufzeit Datentypen inferieren, Optimierungen anwenden und dadurch Berechnungen beschleunigen kann. PyPy ist eher kein universeller Beschleuniger, der beliebigen Python-Code einfach so super schnell macht. Das Bottleneck in deinem Skript sind jedoch Seiteneffekte (I/O, sleep-Funktion, ...). Wie du das verbessern kannst, können andere hier vmtl. besser erklären.
einfachTobi
User
Beiträge: 511
Registriert: Mittwoch 13. November 2019, 08:38

Du musst ja nicht immer warten, sondern nur prüfen, ob der letzte Tastendruck eine gewisse Zeit her ist. Beim Tastendruck also die Zeit messen und vergleichen mit dem letzten Wert.
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Micki: Bevor man da etwas beschleunigen will, sollten da a) die ``global``\s verschwinden, b) der Programmfluss so gestaltet werden das sich die beiden Funktionen nicht immer gegenseitig aufrufen, das wird nämlich nur eine zeitlang gut gehen, und c) ordentliche Namen verwendet werden.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Micki
User
Beiträge: 34
Registriert: Freitag 20. März 2020, 09:17

@ blackjack und @ einfachTobi:
Vielen Dank für Eure Antworten!

Wie ihr am Skript sehen könnt, bin ich Newby. Es sei mir also die Frage erlaubt: Wie geht das?
- Zeit prüfen
- globals verschwinden (Ich muss vorher definieren ...)
- Aufruf von Funktionen ... Das ist ne Schleife und die Funktionen werden im Bedarfsfall aufgerufen...
- was sind ordentliche Namen?
Benutzeravatar
noisefloor
User
Beiträge: 4159
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Jetzt habe ich gelesen, dass man mit PyPy eine Steigerung der Geschwindigkeit um das 5fache hinbekommen soll.
Wie schon oben steht: das pauschal falsch. Es gibt Programme, die sind I/O-bound, d.h. I/O ist der geschwindigkeitsbestimmende Faktor. D.h. das Programm wartet meistens auf I/O. Das ist bei dir der Fall. Es wird kaum gerechnet, sondern auf MIDI-Daten oder GPIOs gewartet. Da bestimmt deine Hardware (und das Protokoll der Datenübertragung) die Geschwindigkeit, nicht die CPU.
Wenn du was rechenintensives hast (wie z.B. Primzahlenberechnung), dann ist das CPU-Bound, weil alleine die Ausführungsgeschwindigkeit des Codes auf der CPU eine Rolle spielt. Und dann ist man mit PyPy i.d.R. schneller, weil PyPy die o.g. Optimierungen zur Laufzeit des Codes vornimmt.
Wie geht das?
global: Funktionen kennen Argumente und Rückgabewerte. Das ist der Weg, nicht `global`. Alternativ kann man auch die benötigten Werte in die Attribute einer Klasse packen.
- Aufruf von Funktionen ... Das ist ne Schleife und die Funktionen werden im Bedarfsfall aufgerufen...
Nein, ist es nicht wirklich. Du rufst 1x `PerCon` , darauf wird `PC` aufgerufen und daraus wieder `PerCon` _neu_. Du kehrst nicht in die ursprüngliche Schleife zurück. Dazu solltest du `return` in `PC` nutzen.

Namen: der heilige Gral der Python-Programmierung ist die PEP8 (einfach mal in Google suchen). Da sind auch die Namenskonventionen für Python Variablen, Funktionen, Klassen etc beschrieben.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ergänzung zu den Namen: Die sollten nicht nur den Konventionen bei der Schreibweise entsprechen sondern die sollten dem Leser vermitteln was der Wert der dahinter steckt bedeutet. `PC()`, `PerCon()`, `ch`, `pc` sind da beispielsweise nicht wirklich hilfreich.

Den magischen Pin-Nummern sollte man auch Namen geben, damit der Code verständlicher wird und weniger fehleranfällig bei Änderungen/Erweiterungen ist.

In dem Quelltext sind auch einige Wiederholungen von sehr ähnlichem Code die so nicht sein sollten.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Sirius3
User
Beiträge: 18227
Registriert: Sonntag 21. Oktober 2012, 17:20

Hier ist wieder einmal ein typisches Beispiel, warum globale Variablen alles viel komplizierter machen. Es wird erst die globale Variable gesetzt, dann PC aufgerufen, statt einfach die zwei Zahlenwerte per Argumente zu übergeben.
Ich frage mich, wo in pc das c in der Abkürzung von program herkommt?

Code: Alles auswählen

import time
import mido
from RPi import GPIO

def send_program_change(channel, program):
    with mido.open_output("USB MIDI Interface MIDI 1") as outport:
        outport.send(mido.Message('control_change',
            channel=channel, control=0, value=0))    #   MSB
        outport.send(mido.Message('control_change',
            channel=channel, control=32, value=0))  #    LSB
        outport.send(mido.Message('program_change',
            channel=channel, program=program))
        print(f'CH = {channel}    PC = {program}')

def send_message(message, channel, note, velocity):
    with mido.open_output("USB MIDI Interface MIDI 1") as outport:
        outport.send(mido.Message(message
            channel=channel, note=note, velocity=velocity))        

def mainloop():
    ton = True
    while True:
        if GPIO.input(5) == 0:    
            # Song start
            time.sleep(0.5)
            send_message('note_on', channel=15, note=27, velocity=65)
            print('Song Start')
        if GPIO.input(22) == 0:      
            # Song Stopp                   
            time.sleep(0.5)
            send_message('note_on', channel=15, note=28, velocity=65)
            print('Song Stopp')
        if GPIO.input(27) == 0:
            time.sleep(0.5)
            send_program_change(0, 1)
        if GPIO.input(17) == 0:
            time.sleep(0.5)
            send_program_change(0, 2)
        if GPIO.input(4) == 0:
            time.sleep(0.5)
            send_program_change(0, 3)
        if GPIO.input(12) == 0:
            time.sleep(0.5)
            send_program_change(0, 4)
        if GPIO.input(25) == 0:
            time.sleep(0.5)
            send_program_change(0, 5)
        if GPIO.input(24) == 0:
            time.sleep(0.5)
            send_program_change(0, 6)
        if GPIO.input(23) == 0:
            time.sleep(0.5)
            send_message('note_on', channel=15, note=9, velocity=65)
            print('All Notes Off')     
        if GPIO.input(18) == 0:
            time.sleep(0.5) 
            value = 127 if ton else 0
            send_message('control_change', channel=0, control=64, value=value)
            print('Damper on' if ton else 'Damper off')
            ton = not ton

def main():
    try:
        GPIO.setmode(GPIO.BCM)
        GPIO.setup([5, 22, 27, 17, 4, 12, 25, 24, 23, 18], GPIO.IN)
        mainloop()
    finally:
        GPIO.cleanup()

if __name__ == '__main__':
    main()
Jetzt müßte man noch die vielen Code-Wiederholungen wegbekommen, und den magischen Nummern noch gute Namen geben.
Micki
User
Beiträge: 34
Registriert: Freitag 20. März 2020, 09:17

Danke!
Schneller ist es leider auch nicht, aber schöner😂

Was kann ich denn machen, um die time.sleeps zu vermeiden. Die brauchen wohl am meisten Zeit.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du darfst sie nicht benutzen, und musst dein programm von einem verschwenderischen Pollen auf ereignis-basierte Programmierung umstellen. Was dir uebrigens in der ersten Antwort hier auf deinen ersten Post schon angeraten worden war. viewtopic.php?f=31&t=47984#p362795
Sirius3
User
Beiträge: 18227
Registriert: Sonntag 21. Oktober 2012, 17:20

Soetwas löst man mit add_event_detect und einer passenden bouncetime. Einfach die Events registrieren, das Ereignis in eine Queue schreiben und im Hauptprogramm nur noch auf Ereignisse aus der Queue reagieren.

Ich glaube, ich wiederhole mich: /viewtopic.php?f=31&t=47984&start=15#p362894
Micki
User
Beiträge: 34
Registriert: Freitag 20. März 2020, 09:17

@Sirius, @deets:
verbale Beschreibungen nutzen mir wenig. Ich brauche da ein Beispiel.
Sirius3
User
Beiträge: 18227
Registriert: Sonntag 21. Oktober 2012, 17:20

Ja, und die Beispiele sind in den von uns verlinkten Beiträgen, wo DU schon exakt das selbe gefragt hattest.
Benutzeravatar
noisefloor
User
Beiträge: 4159
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

du verwendest doch das RPi.GPIO Modul - das hat eine Doku inkl. Beispielen.

Gruß, noisefloor
Antworten