Coden, aber effizient!

Wir leben im digitalen Zeitalter (na gut, die meisten von uns), und langsam aber sicher wird vielen Entscheidern klar, dass die AWS-Cloud (oder ihre Verwandten) nicht nur total praktisch ist, sondern auch eine ganze Menge Energie verbraucht. Schätzungen sprechen von bis zu 20% des Energieverbrauchs der ganzen Welt. Wohlgemerkt sind Anwendungen in der Cloud immer noch sparsamer als eigene Rechenzentren mit Servern aus Blech, die 24 Stunden an der Steckdose nuckeln, aber beispielsweise nur tagsüber benötigt werden. Cloud-Instanzen sind üblicherweise “shared” und verbrauchen nur dann Energie, wenn benötigt. Trotzdem bedeuten mehr Cloud-Instanzen natürlich auch mehr Energieverbrauch (und CO2-Ausstoß, sofern das Rechenzentrum keinen grünen Stromanschluss besitzt).

Tatsächlich können wir die Frage nach dem Energieverbrauch auch Codern und Software-Architekten stellen: Benötigt euer Software-System wirklich 10 Instanzen und 3 Datenbanken? Muss für eine eher simple Anwendung 1 GB RAM reserviert werden und die Kiste mit dem fettesten Prozessor oder darf es ein bisschen weniger sein? Sollte der Energieverbrauch einer Plattform vielleicht sogar zu den Entscheidungskriterien gehören?

Der Vergleich

Für die 2. Auflage meines Buchs “Besser coden” habe ich ein Kapitel über effizienten Code geschrieben – und ein paar Messungen durchgeführt. Dazu habe ich eine relativ einfache Webanwendung in mehreren Sprachen geschrieben und Aspekte wie Performance, Ressourcenverbrauch und Anspruch an Entwickler verglichen. Letzteres ist nicht zu unterschätzen: Spart eine Technologie Speicher, aber Sie finden keinen Entwickler, der sie beherrscht, bleibt ihr tolles Softwaresystem graue Theorie.

Es traten an:

  • Java 13 und Spring Boot, das beliebte Framework für Webservices
  • PHP 7.4, eine bewährte, einfache Skriptsprache mit Cache APCu
  • Rust 1.52 und Actix Web, eine ziemlich neue Sprache samt passendem Webservice-Framework
  • sowie quasi als Online-Bonus (nicht im Buch) Go.

Der Webservice besitzt nur einen einzigen Endpoint, der dafür gedacht ist, ein Wort gegen eine hinterlegte Liste zu prüfen. Eine solche Funktion ist beispielsweise in einem Scrabble-Spiel nötig: Ist das gelegte Wort erlaubt oder nicht? Das Ergebnis wird dabei als JSON-Antwort formuliert.

Die Liste ist absichtlich nicht in einem ausgewachsenen Datenbanksystem hinterlegt, denn ich möchte nicht die Effizienz unterschiedlicher RDBMS bewerten, sondern die von Software-Plattformen. Daher lädt die zu schreibende Anwendung die Wortliste beim Start aus Textdateien und hält sie dann im RAM. Im Test enthielt diese Liste knapp 180.000 Einträge. Im Fall von PHP erfordert eine solche Vorgehensweise zwingend den Einsatz eines Caches (hier verwendet: APCu), um die Dateien nicht bei jedem Aufruf des Skripts erneut laden zu müssen.

Die Rechenzeit habe ich mit dem Apache Benchmark ab gemessen, einmal einen Einzelrequest und einmal 10.000 auf einmal in sechs parallelen Threads, um die Leistung im Parallel Processing zu bestimmen.

Den Code finden Sie in Grundzügen in meinem Buch (bis auf die Go-Version). Hier fasse ich Ihnen nur die Ergebnisse zusammen:

Java/Spring BootPHP/APCuRust/ActixGo
RAM-Verbrauch50 MB200 MB0,9 MB24 MB
Anwendungsgröße19 MB (JAR)372 Bytes (Skript)8,4 MB (binär)7,1 MB (binär)
Zeit 1 Aufruf1,8 ms0,9 ms0,4 ms0,5 ms
Zeit 10.000 Aufrufe1,1 s0,6 s0,5 s0,5 s
Startup-Dauer2,5 snicht messbar53 ms75 ms
Buildtime7,4 sentfällt69 s1 s
Coding-Anspruchleichtsehr leichtschwierigmittel

Sie sehen, dass das rein binäre Rust-Programm zur Laufzeit am schnellsten und genügsamsten ist – aber finden Sie mal einen Rust-Entwickler auf dem Jobmarkt oder lernen Sie die Sprache “mal eben”! Ich hab letzteres versucht und brauchte mehrere Packungen Schokokekse, um die spezielle Speicherverwaltung zu kapieren. Die lange Buildtime ist dabei dem anspruchsvollen Compiler- und Linker-Vorgang geschuldet.

Abgesehen vom RAM-Verbrauch ist PHP unter dem Strich wohl die effizienteste Lösung. Aber viele Entwickler scheuen sich davor, größere Projekte in PHP anzulegen – die fehlende starke Typisierung und die immer über uns Entwicklern schwebende Versuchung, spaghettimäßig PHP- und HTML-Code zu mixen, sowie ein paar Fallen wie vergessenes $this->, sind klare Minuspunkte. Dafür ist die Turnaround-Zeit Null: Skript nur speichern, schon ist es bereit zum Aufruf per HTTP.

Java ist nicht ohne Grund sehr beliebt. Aber die Java-Runtime, so optimiert sie mittlerweile auch ist, geht alles andere als sparsam mit Ressourcen um und ist merklich langsamer als die Binärcode-Konkurrenz (auch PHP verwendet dank Zend-Engine letztlich Binärcode). Ein Maven-Buildprozess lädt gefühlt mehrmals täglich das halbe Internet runter. Dafür ist der Code (speziell mit Spring Boot) aufgeräumt und vergleichsweise leicht zu debuggen. Große Projekte mit komplexer Geschäftslogik sind in Java wohl vergleichsweise am lesbarsten abzubilden.

Fazit

Sie sehen: Es gibt keine Lösung, die gleichzeitig einfach und technisch effizient ist. Sie müssen immer abwägen: Lohnt es sich, in eine hocheffiziente, moderne Technik wie Rust oder Go zu investieren? Oder setzen Sie auf eine bewährte und
einfache Technik wie Java und nehmen in Kauf, dass Sie mehr
Server benötigen (und Energie verbrauchen), wenn mehr Rechenpower erforderlich ist? Gerade bei neuen Projekten ist es sicher eine gute Idee, über diese Fragen zu diskutieren. Denn später können Sie die Plattform nicht mehr einfach ändern.

So bleiben vermutlich noch auf Jahre oder Jahrzehnte Java-Webservices
und PHP-Skripte state of the art – obwohl mit Rust oder Go, C++20, D …
technisch hochmoderne und extrem effiziente Konkurrenzprodukte be-
reitstehen.

Mein Dank für die Mitarbeit geht an Marcus Schlechter.