Python vs. C++

  • Hallo zusammen,


    ich wollte mal nachfragen, wie eure Erfahrungen sind, wenn man an dem Raspberry Pi mit Python oder mit C++ einige Codes umsetzt. Sicherlich könnte man auch C nehmen. Ich erkläre mal den Background. Ich habe einen Roboter selber gebaut und mit Python und ROS programmiert. Das ist jetzt gute zweieinhalb Jahre her. Python war für mich einfach die Wahl, weil es einfacher ist und weil ich für viele Komponenten auf Libraries zurückgreifen konnte. Nun ist es ja kein Geheimnis, dass C++ eigentlich perfomanter ist, als Python:


    C++ ist schneller als Python, weil es statisch typisiert ist, was zu einer schnelleren Kompilierung des Codes führt. Python ist langsamer als C++, da es dynamische Typisierung unterstützt und außerdem einen Interpreter verwendet, was den Kompilierungsprozess verlangsamt.


    Ich möchte meinen Roboter "neu" bauen. Ich tausche Motoren und Servos mit welchen, bei denen ich auch ein Feedback bekomme.


    Warum ich hier Erfahrungswerte zu C++ suche ist, weil ROS von Haus aus C++ und Python mit bringt. Bei ROS Kinetic gab es mal noch einen experimentellen Ansatz für Java. Da Java in einer virtuellen Maschine (JVM) läuft, wäre es zwar plattformunabhängig, aber die Interaktion mit der Hardware wäre auch langsamer. Außerdem orientiert sich ROS Kinetic an Ubuntu 16.04, was EOL ist.


    Ich kann natürlich auch Python-Code in einem C++-Code umsetzen, was mir helfen könnte, gewisse Abhängigkeiten zu umgehen.


    Wenn ich nun die Möglichkeiten in C++ anschaue, dann stößt man extrem oft auf WiringPi und auf pigpio. Was man inzwischen auch oft findet und raushört, ist, dass WiringPi nicht mehr fortgesetzt wurde und dass pigpio sowohl robuster als auch umfangreicher sei.


    Ich habe folgendes schon gesehen:

    https://elinux.org/RPi_GPIO_Code_Samples


    Auf einen direkten Zugriff der Register würde ich sehr gerne verzichten. Ist mir definitiv zu kompliziert.


    Ich habe hier ein Beispiel zu einem HC-SR04 gefunden in WiringPi:

    https://github.com/omaraflak/HC-SR04-Raspberry-Pi-C-.git


    Dies dürfte ich wahrscheinlich korrekt übersetzt haben zu pigpio:

    https://github.com/Michdo93/HC-SR04-Raspberry-Pi-C-


    Ich kann dies erst in ein paar Tagen verifizieren. Auch habe ich für einen Parallax PING))) den Python Code zumindest mal von Python zu C++ (WiringPi) übersetzt. Da muss ich auch mal austesten, wie dass mit WiringPi allgemein klappt und wie mit pigpio. (Man kann WiringPi auch mit einer inoffiziellen Version scheinbar installieren).


    Auf dem Raspberry Pi habe ich noch einen L298N H-Brücke als Motor angeschlossen. Im obenstehenden Link zu den verschiedenen Programmierungen mit dem GPIOs steht ja auch drinnen, wie ich ein PWM-Signal schicke. Für das Betreiben einer einfachen Motorsteuerung wäre mir das ausreichend genug, dass ich meinen Python-Code zu C++ (pigpio oder auch WiringPi) übersetzen kann.


    Komplizierter wird für mich jetzt aber alles andere, was mit SPI oder auch mit I2C zu tun hat. Ebenfalls habe ich auch testweise den Arduino Nano seriell am Raspberry Pi angeschlossen. Wie ich auf dem Arduino seriell schreibe, ist mir klar. Wie ich auf dem Raspberry Pi seriell mit Python lese, auch. Problem: Python!


    Ein gutes Beispiel hier mit Python wäre:

    https://roboticsbackend.com/ra…ino-serial-communication/


    Mit Python habe ich den HC-SR04 auch über seriell, UART und I2C getestet, wenn ich diesen am Arduino angeschlossen habe. Dies würde ich gerne auch für das Verständnis mit C++ nachstellen.


    Für WiringPi kann ich mich an den Vorschlag von Stackoverflow orientieren:

    https://stackoverflow.com/ques…n-rpi-and-arduino-using-c


    Einen wirklichen Ansatz zu pigpio habe ich nicht gesehen. Ein einfaches Beispiel für das serielle Lesen einmal per USB-Kabel und einmal per GPIO-Pins, wären für mich sehr hilfreich.


    Bei SPI nutze ich spidev in Python, um einen MCP3008 zu verwenden. Nachfolgen kann ich spidev auch in C++ nutzen:

    https://medium.com/geekculture…spi-and-uart-4677f401b584


    Bei I2C nutze ich ebenfalls smbus2, im eben geposteten Link wird smbus in C++ verwendet. Das sollte mir eigentlich genügen, da ich dort weder WiringPi noch pigpio benötige. Bei der dort gezeigten Lösung für UART bin ich mir noch nicht sicher, ob dass für das zuvor beschriebene Problem bereits meine Lösung wäre.


    Ich komme mal wieder anwendungsbezogener. Der Parallax PING))) und der HC-SR04 haben keinen Treiber an für sich. Dort brauche ich nur eine robuste und schnelle Möglichkeit für den Zugriff auf die Pins der Raspberry Pi's. Davon setze ich mehrere ein. Für meinen Infrarotabstandssensor der GP2Y0A02YK0F*-Familie, benötige ich einen Analog-Digital Wandler, den MCP3008. Für diesen brauche ich SPI und spidev, was scheinbar auch in C++ umsetzbar ist. Den Gleichstrommotor tausche ich mit vier Gleichstrommotoren aus. Heißt statt einem L298N verwende ich nun zwei. Hierfür brauche ich prinzipiell Minimum den Raspberry Pi. Dort kann ich für PWM die Pins 12, 32, 33 oder 35 einsetzen. Ich werde dies jedoch nur mit einem einzigen Motor testen.


    Bei RPi GPIO Codes Samples sehe ich für pigpio:


    Code
       /* Start 75% dutycycle PWM on GPIO17 */
       gpioPWM(17, 192); /* 192/255 = 75% */


    Also ist prinzipiell auch mit pigpio dies recht direkt umsetzbar und ich kann meinen Python-Code für einen einfachen Motor einfach übersetzen.


    Ich bin ehrlicherweise nur irritiert, warum ich hier den GPIO17 verwende... Für mich ist der GPIO17 dasselbe wie Pin 11. Meiner Meinung nach muss ich GPIO18 (Pin 12), GPIO12 (Pin 32), GPIO13 (Pin 33) oder GPIO19 (Pin 35) verwenden.


    Für den Raspberry Pi sehe ich bei einem Motor, der einen Encoder verwendet, keine wirkliche Anleitung. Nur Encoder, die nicht direkt sich an einem Motor befinden. Diese liefern meist 3,3V zurück. Bei meinem Motor steht dran, dass diese 3,3V - 5V zurückliefern können. Ich habe mal Logic Level Shifter bereits gelötet. Habe auch bei einem anderen Motor mit einem Encoder, der 3,3V zurückliefert schon ein kleines Python-Programm geschrieben, der sich nur um den Encoder kümmert. Denke auch hier entsteht dann mal in naher Zukunft sowohl ein Python, als auch ein C++-Tutorial.


    Wesentlich exakter ist es, einen Servotreiber zu verwenden. Sprich das PWM-Signal kommt vom Servotreiber. Hierfür habe ich einen PCA9685 vorgesehen und eingeplant. Bei pigpio sehe ich hierzu leider nur ein Python-Beispiel.


    Einen nicht funktionierenden Ansatz in C++ mit pigpio sehe ich hier:

    https://forums.raspberrypi.com/viewtopic.php?t=213033


    Es gibt irgendeinen C++-Ansatz mit NXP:

    https://github.com/TeraHz/PCA9685


    Hab jetzt auch nicht auf die Schnelle rausgefunden, was NXP sein soll.


    Ich sehe hier einen schönen WiringPi-Ansatz:

    https://github.com/Reinbert/pca9685


    Hier gibt es einen bcm2835-Ansatz:

    https://github.com/vanvught/rp…2/tree/master/lib-pca9685


    Ich bin noch unentschlossen, ob ich diesen einfach zusätzlich nehmen sollte oder ob ich selbst die Mühe mir machen muss, um einen C++-Ansatz für pigpio zu entwickeln.


    Am PCA9685 werden natürlich auch verschiedene Servos angeschlossen. Zum Beispiel für ein Pan-Tilt-Kit der Kamera. Daher wäre es ein wichtiges Schlüsselelement. Auch hier gibt es wenige Python-Tutorials, wie ein Servo mit analogem Feedback verwendet wird. Aber es gibt welche. Es geht einfach nur ein Female-Jumper-Wire zu einem GPIO-Pin auf dem Raspberry Pi. Bei Arduinos gibt es ein wenig mehr Tutorials, wie man dort das analoge Feedback ausliest. Sollte aber nicht das große Problem werden.


    Als IMU verwende ich einen Raspberry Pi Sense HAT. Einfach, weil's einfach ist. Dessen Library ist in C geschrieben:

    https://github.com/raspberrypi/rpi-sense


    Die hierfür verwendete RTIMU Library ist sogar in C++ geschrieben.


    Bei der Sense HAT habe ich hier ein gutes Beispiel gefunden:

    https://github.com/PhilippeSimier/SenseHat


    Teilweise schlecht dokumentiert. Liest sich viel nach französisch-englisch. Aber alles in allem recht brauchbar. Hauptsächlich muss ich per I2C mit der Platine kommunizieren. Für Python gibt es eine gute Dokumentation, auch für Kinder. Hier würde ich auch einige Programmcodes vergleichen, die ich eben nicht nur für den Roboter brauche. Also bspw. die Temperatur.



    Viele setzen ja auf pigpio statt WiringPi. Ich könnte hier wie gesagt Hilfe gebrauchen, wie ich einige Programmcodes übersetzen könnte. Teilweise zu WiringPi und definitiv zu pigpio. Bauchschmerzen macht mir eigentlich der PCA9685.


    Ich würde mich in ein paar Tagen noch einmal melden, wenn ich manches durchgetestet habe. Neben der unterschiedlichen Umsetzung in Python, C++ (WiringPi und pigpio), welche Vergleichsparameter wären denn wichtig? Beim HC-SR04 habe ich noch nie gemerkt, dass die Pins nicht robust ihre Spannung halten würden. Die CPU-Auslastung soll hier jedoch hochgehen. Also macht es meiner Meinung nach Sinn, die Programme jeweils einzeln laufen zu lassen und zu prüfen, wie die CPU-Auslastung ist. Ich habe auch nie Verzögerung festgestellt was hier die Abstandsmessung angeht. Etwas von oben runtergelassen und vor den Sensor gehalten und meine Messung hat auch direkt einen kürzeren Wert ergeben. Hier halte ich es fast schwer bis unmöglich, eine Zeitmessung vorzunehmen, bis ein Sensor einen neuen Wert erfasst. Vielleicht hat hier jemand ein mögliches Prüfkriterium im Kopf.


    Jetzt gibt es Erfahrungswerte, die ich nicht prüfen will:


    • Der PCA9685 ist präziser was PWM angeht.
      • Servomotoren können damit präziser gesteuert werden.
      • Auch Gleichstrommotoren können besser gesteuert werden. Es gibt sogar eine wissenschaftliche Arbeit darüber, dass der Duty Cycle am besten über PWM gesteuert wird.
    • Ultraschallsensoren funktionieren robuster und präziser als Infrarotabstandssensoren.
    • Der Parallax PING))) ist genauer als der HC-SR04. Hab ich alles schon mit einem Meterstab gemessen.


    Für Kameras will ich kein Treiber schreiben. Denn für die Kamera kann ich den Raspicam Node in ROS verwenden:

    https://github.com/UbiquityRobotics/raspicam_node


    Der funktioniert aus Erfahrung gut. Für weitere Kameras wäre meiner Meinung nach OpenCV in C++ ganz nützlich, weil ich mittels OpenCV Bridge OpenCV Bilder in ROS Bilder umwandeln kann.


    In ROS sind erfahrungsgemäß auch die Latenzen bei ROS Publisher und Subscriber geringer, wenn man C++ statt Python nutzt. Auch das werde ich hier nicht darstellen, sondern ich konzentriere mich auf Standard-Codes ohne ROS, damit man diese genannten Komponenten auf dem Raspberry Pi auch mit C++ betreiben kann.


    Würdet ihr hier weiterhin pigpio bevorzugen? Welche Tipps habt ihr noch mitzugeben? Hab ich irgendetwas ausgelassen, dass wichtig werden könnte? Ich hoffe, dass ich bis Heilige Drei Könige dann soweit durch bin und hier immer wieder Lösungen präsentieren kann, da dies für mich nebenberuflich abläuft und ich da denke ich schon hin und wieder paar Tage brauche, bis ich eine Lösung präsentiere. Diskussionen sind dann herzlich willkommen.


    Liebe Grüße

  • Michdo93: Die Kompilierungsgeschwindigkeit ist relativ egal und ich habe auch den Eindruck Du verwechselst so ein bisschen Laufgeschwindigkeit und Kompilierungsgeschwindigkeit; und kompilieren in nativen Maschinencode und statische Typisierung. Was die Plattformunabhängigkeit angeht dürften sich hier C++, Java, und Python nichts nehmen. JVM bedeutet nicht, das es nicht auch einen JIT compiler geben kann, und einiges dann am Ende nicht doch in nativem Maschinencode läuft. So etwas gibt es mit PyPy auch für Python.


    Ich denke die Frage ob Python oder C++ ist in den meisten Fällen weniger eine Frage der Ausführungsgeschwindigkeit, sondern der Geschwindigkeit in der man Programme entwickeln kann. Denn Python setzt zum Ansprechen der GPIOs am Ende des Tages ja auch auf wiringPi oder pigpio und es gibt viele Bibliotheken die unter der Haube C, C++, Fortran, … benutzen, an den Stellen wo es auf rohe Rechenleistung ankommt. Diese Strategie kann man auch in eigenen Python-Programmen verfolgen. Falls es Leistungsprobleme gibt, messen wo die liegen, und dann den Falschenhals in Cython oder einer anderen Programmiersprache beseitigen, und in das Python-Programm einbinden.

    “Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.” — Terry Pratchett, Jingo

  • Sicherlich könnte man auch C nehmen.

    Wenn das reicht? C++ erlaubt durch die OO Eigenschaft Libraries leichter einzubinden und zu nutzen.


    Wegen der Geschwindigkeit wuerde ich mir keine Sorgen machen. Nimm die Sprache die Du besser kannst. Da Du mit Python angefangen hast ist vermutlich Python die bessere Wahl :)

    "Really, I'm not out to destroy Microsoft. That will just be a completely unintentional side effect."

    Linus Benedict Torvalds, 28.9.2003


    Hast Du die Woche schon Deine Raspberry gesichert :fies: Bei mir tut das raspiBackup automatisch :shy:

  • C/C++ fuer dein Projekt:


    Die serielle Schnittstelle kannst du erst mal wie ein File behandeln. Das Problem dabei ist das blockierende read(). Du brauchst also einen Thread oder select() / poll() damit das Programm nicht dort haengt.


    Selbstverstaendlich kann man die Schnittstelle auch als tty benutzen. Dann hast du mehr Kontrolle und kannst mit ioct() abfragen wie viele Bytes im Buffer warten. Knifflig ist die Initialisierung, denn das tty muss in den Raw-Mode gesetzt werden.


    Der AD-Wandler sollte kein Problem sein. Das Beispiel ist zwar fuer einen MCP3202, aber der MCP3008 muesste eigentlich aehnlich arbeiten: https://blog.heimetli.ch/raspb…2-12bit-ad-converter.html

  • Um eine Entscheidung zur Programmiersprache zu fällen, würde ich das Entscheidungskriterium "statische versus dynamische Typisierung" ganz nach hinten stellen. Richtig ist, dass C++ aufgrund des systemnahen Ansatzes tendenziell deutliche Vorteile hat, schnelle Programme zu erzeugen. Richtig ist aber auch, dass man C++ recht einfach diesen Vorteil verspielen kann und es auch möglich ist, mit Python effiziente Programme zu erstellen. Soll heißen, die Sprache unterstützt per se zwar das Erstellen schneller Programme, aber man muss dennoch die Sprache sehr gut kennen, um die Vorteile auch wirklich zu realisieren.


    Ich würde an Deiner Stelle mal die Grenzen definieren, welche Geschwindigkeit bei der Ausführung mindestens 'anliegen' muss, damit Dein Projekt gelingen kann. Und dann würde ich einfach mit Python testen, ob Du über diese Grenzen hinaus kommst oder ob Python dafür zu langsam ist. Wenn Du mit Python die Grenzen sprengst, ist die Entscheidung gefällt. Anderenfalls wäre C++ der Plan B.

    Mein Github-Repository ist hier zu finden.

  • Late und early binding hat praktisch keinen Einfluss auf die "Geschwindigkeit" des Kodes. O(1..). Andere Faktoren sind maßgeblicher. Wenn das Python zu langsam ist, wird es auch in C(pp) zu langsam sein. :(

  • Late und early binding

    Ich denke es geht hier mehr um Duck Typing. Das ist eine Faehigkeit der interpretativen Sprachen wie Python oder Smalltalk. Hat aber nichts mit Laufzeitgeschwindigkeit zu tun :no_sad:

    "Really, I'm not out to destroy Microsoft. That will just be a completely unintentional side effect."

    Linus Benedict Torvalds, 28.9.2003


    Hast Du die Woche schon Deine Raspberry gesichert :fies: Bei mir tut das raspiBackup automatisch :shy:

  • framp Naja, indirekt schon weil Duck Typing und das Ausmass an ”Dynamik” die Python erlaubt, dem Compiler eine ganze Menge Möglichkeiten der Optimierung nimmt. Die Grenze erreicht da so etwas wie PyPy, das obwohl dort ein JIT-Compiler nativen Maschinencode zur Laufzeit erstellt, trotzdem noch bei jedem Aufruf getestet werden muss, ob die Typen zum erstellten Maschinencode passen, selbst wenn sie das immer tun, weil für potentiell andere Typen dann wieder der JIT-Compiler laufen muss. Das ist bei Java anders, weil dort der JIT-Compiler einmal Maschinencode für die durch die statische Typisierung bekannten Typen erzeugen kann, und bei jedem Aufruf dann einfach diesen einen Code ausführen kann, weil die Typen immer gleich sein werden.


    Und das kann man auch als Unterschied zwischen late und early binding sehen. Bei C++ und Java sind die Typen in Signaturen zur Übersetzungszeit bekannt. Bei Python ist letztlich jedes Argument *garantiert* nur von `object` abgeleitet, aber man weiss nicht einmal ob die Funktion oder Methode zur Laufzeit nicht noch ersetzt wird, also nicht nicht einmal die tatsächliche Signatur von Aufrufen nachdem der Quelltext in Bytecode übersetzt wurde.

    “Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.” — Terry Pratchett, Jingo

  • Hm ... stimmt. In C++ mit static binding kann sofort per VFT zum Ziel gesprungen werden - ausser man nutzt Reflection. Bei Python oder jeder anderen late binding / Duck Typing Sprache muss erst Code ausgefuehrt werden der testet ob die Methode im Zielobject existiert. Da das Object immer anders sein kann ist da auch nicht viel mit Laufzeitoptimierung zu machen.


    Ergo sind static binding Sprachen in diese Beziehung schneller.


    Davon abgesehen bin ich kein Fan von Ducktyping Sprachen. Ich habe lieber zur Compilezeit eine Fehlermeldung dass Methoden nicht verfuegbar sind als spaeter in Produktion wo zur Laufzeit festgestellt wird dass ein Object keinen Methodenaufruf versteht.


    Wenn der Code @home laeuft ist das etwas anderes. Da ist ein "Do not understand" Fehler (An den kann ich mich noch bei Smalltalk erinnern) nicht so gravierend - ausser man betreibt ein AKW zu Hause :lol:

    "Really, I'm not out to destroy Microsoft. That will just be a completely unintentional side effect."

    Linus Benedict Torvalds, 28.9.2003


    Hast Du die Woche schon Deine Raspberry gesichert :fies: Bei mir tut das raspiBackup automatisch :shy:

  • Wenn das in Production-Code auftaucht, dann hat man eventuell nicht genug Unit-Tests beziehungsweise keine ausreichende Testabdeckung. Das kommt in der Praxis nicht so wirklich oft vor.

    “Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.” — Terry Pratchett, Jingo

  • Das kommt in der Praxis nicht so wirklich oft vor.

    Vielleicht in Deiner Praxis.


    Aber im SW Development gibt es das Dreieck aus Kosten/Zeit/Qualitaet. Wo wird als erstes gespart?

    "Really, I'm not out to destroy Microsoft. That will just be a completely unintentional side effect."

    Linus Benedict Torvalds, 28.9.2003


    Hast Du die Woche schon Deine Raspberry gesichert :fies: Bei mir tut das raspiBackup automatisch :shy:

  • Das ist wirklich eine unberechtigte Angst, dass ohne Typprüfung doch dauernd solche Sachen passieren müssten. Und es ist selten, dass so etwas bis in den Produktionseinsatz durchrutscht. Selbst ohne volle Unit-Test Abdeckung probiert man doch Code den man geschrieben hat, doch wenigstens manuell mal aus. Auch bei statisch typisierten Programmiersprachen, denn der Compiler findet ja keine Logikfehler. Automatisierte Tests sind in Python (und anderen dynamischen Programmiersprachen) oft auch wesentlich leichter geschrieben. Das ”pythonische” Rahmenwerk dafür ist `pytest`. Mock-Objekte sind durch die dynamische Natur der Sprache einfach zu schreiben, beziehungsweise braucht man oft gar keine schreiben, sondern kann generische verwenden. Die Standardbibliothek hat da ein Modul für (`unittest.mock`).

    “Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.” — Terry Pratchett, Jingo