arrow arrow--cut calendar callback check chevron chevron--large cross cross--large download filter kununu linkedin magnifier mail marker menu minus Flieger phone play plus quote share

Green IT: Welchen Einfluss haben Datenformate auf Performance und den CO2-Verbrauch?

Ein Experiment

In unserem Alltag übertragen wir immer wieder Daten: Zwischen Fremdsystemen und eigenen Services, zwischen Microservices oder zwischen Front- und Backends. Dass Datentransfers Energie verbrauchen, ist kein Geheimnis. In der Entwicklung von mobilen Apps wird auch oft auf den Stromverbrauch geachtet, aber wie steht es anderswo?

Im Rahmen unserer Green-IT-Initiative bei Cologne Intelligence wollten wir der Frage nachgehen, ob sich der Energieverbrauch von Algorithmen messen lässt und wie deutlich diese Ergebnisse sind. Dafür haben wir mal ein paar Dinge selbst ausprobiert.

Foto von Jochen Wierum
Jochen Wierum

Software Developer

Unser Szenario

Eine gute Nachricht vorab: als Daumenregel lässt sich festhalten, dass performante Algorithmen normalerweise auch die energiesparenderen sind. Komplizierter wird es jedoch, wenn Parallelisierung ins Spiel kommt. Wir wollten es genauer wissen und haben uns ein Test-Setup überlegt.

Unser Szenario ist einfach: wir wollen eine Liste von Daten übertragen. Dazu gibt es einen Server und einen Client. Die Daten sind eine große Liste, welche ein paar Zahlen und ein paar Strings enthalten. Wir haben diese Nutzlast als angemessen empfunden, denn viele „echte“ Schnittstellen sehen ähnlich aus.

Wir haben uns entschieden, den Datensatz viele tausend Male abzurufen. Da wir unsere Lösung in Java entwickelt haben, können wir so auch die Optimierung durch die JVM zur Laufzeit berücksichtigen.

Messen wollten wir neben der reinen Zeit auch den Energieverbrauch. Eine kritische Betrachtung über die allgemeine Gültigkeit solcher Ergebnisse soll dabei nicht fehlen.

Die Kandidaten

Die zu übertragende Liste haben wir fest eincodiert – wir wollten nicht zusätzlich den Zugriff auf eine Datenbank messen, sondern uns auf einen Aspekt konzentrieren.

Als Framework für den Server haben wir Spring Boot gewählt. Dort haben wir Endpunkte bereitgestellt, die die Daten in unterschiedlichen Formaten zurückgegeben haben. Wir haben Spring Boot 2, Spring Boot 3 und Spring Boot 3 mit nativer Compilierung verglichen:

Als zugrundeliegendes Protokoll wurde stets http/1.1 verwendet. Die Implementierung der Serverlogik passt auf eine Bildschirmseite – hier ein Auszug:

Wir haben bewusst darauf verzichtet, einzelne Funktionen stark zu optimieren, weil wir die Standardeinstellungen testen wollten. Selbstverständlich gibt es teilweise noch deutliche Spielräume.

Was messen wir? Und wie?

Spannend wurde es bei der Frage, was wir eigentlich messen wollen. Klar war, dass uns die Performance interessierte. Deshalb war die „wallclock-time“ für uns wichtig: das ist die Zeit, die tatsächlich vergeht, bis die Operationen abgeschlossen sind. Doch es gibt weitere Metriken, die wir heranziehen könnten.

Die Wallclock-Time umfasst alles, was bei der Übertragung dazugehört. Wenn eine Festplatte anlaufen muss, wird dies ebenfalls gemessen. Auch ein „Thread.sleep()“ wird gemessen. Aus Nutzersicht verbraucht all das Zeit – und Energie. Solche Punkte fließen in die sonst gerne betrachtete Groß-O-Notation gar nicht ein. Auch die häufig gemessene Anzahl von CPU-Zyklen interessiert sich nicht für Dinge, die außerhalb des CPUs geschehen. Wir haben uns jedoch dazu entschieden, die Nutzerperspektive als Grundlage zu verwenden.

Damit können wir etwas zu der Zeit aussagen, aber wie steht es mit der nötigen Energie? Auch hier hatten wir eine große Auswahl: insbesondere für Intel-CPUs stehen Werkzeuge zur Verfügung, um die Stromaufnahme einzelner Prozesse zu messen. Auch hier wird der Energieverbrauch des Netzwerkes oder einer Festplatte ignoriert. Wir haben uns dazu entschieden, stattdessen die gesamte Stromaufnahme des Rechners zu ermitteln. Auch dieses Vorgehen führt zu Unschärfe: Verfügt das Testgerät über einen Akku, sollte dieser voll sein; wenn das Betriebssystem während der Messung nach Updates sucht, verbraucht dieser Vorgang Energie. Es galt also, eine „Laborumgebung“ herzustellen, die möglichst nur unsere Software misst – mit allem, was dazugehört.

Und der CO2-Verbrauch? Der lässt sich aus der verbrauchten Energie bestimmen. Zugegeben: der Titel dieses Artikels ist Clickbait: wir haben nicht einmal die aufgenommene Leistung in CO2 umgerechnet. Hierfür müsste man unter anderem den verwendeten Strommix in die Berechnung einbeziehen. Aber wir können aus dem Energieverbrauch ableiten, wie sich der CO2-Verbrauch der verschiedenen Messungen zueinander verhalten wird. Das genügte uns vorerst.

Das Setup

Nachdem wir uns entschieden hatten, den Energieverbrauch und die Zeit zu messen, mussten wir uns überlegen, wie wir das anstellen wollten. Wir haben uns für einen einfachen Aufbau entschieden:

Ein Laptop wurde mit einem Ubuntu-Linux ausgestattet. Der Laptop wurde so konfiguriert, dass er auf die Konsole bootete, einen SSH-Daemon startete und sich mit dem Netzwerk verbinden konnte. Alle unnötigen Dienste wurden deaktiviert. Der Monitor wurde ebenfalls abgeschaltet, der Akku war vollgeladen. Auf dem Gerät haben wir Java und die zu testenden Clients und Server installiert. Die Software hat also über „127.0.0.1“ kommuniziert und verwendet damit einen Teil des Netzwerk-Stacks. Würden Client und Server auf unterschiedlichen Geräten laufen, würden wir vielleicht auch die nötige Energie für die (W)LAN-Strecke mitmessen wollen.

Das Netzteil des Notebooks haben wir in ein Shelly-Plug-S gesteckt, ein praktisches IoT-Gerät, welches primär als WLAN-Schalter gedacht ist, aber das auch den Stromverbrauch des angeschlossenen Geräts im Sekundentakt auslesen kann. Es gibt mehrere Varianten, wie die Steckdose angesprochen werden kann. In diesem Experiment haben wir MQTT verwendet, wobei der MQTT-Server logischerweise nicht auf dem Testgerät lief.

Dann kam ein zweites Notebook ins Spiel. Dieses hat zunächst eine Nullmessung gemacht, um die Energieaufnahme des Testgeräts zu bestimmen. Die gute Nachricht: die Messung hat kaum geschwankt, d.h. die Energieaufnahme war über Minuten hinweg ziemlich konstant.

Dann haben wir einen der Server mit zugehörigem Client gestartet. Der Client sollte 100.000 Nachrichten abrufen, dann hat er sich wieder beendet. Wir haben auf unserem Mess-Notebook die Zeit gestoppt und den Energieverbrauch protokolliert.

Dann haben wir dem Testsystem ein bisschen Zeit gegeben, um sich zu entspannen, ehe wir den nächsten Test gestartet haben.

Die Ergebnisse

Wir hatten uns vorher ein paar Gedanken gemacht:

  • Die Nullmessung sollte geringer sein, als alle anderen Messungen
  • Wir haben vermutet, dass Binärformate (Protobuf, Serialisierung, CBOR) schneller sein müssten als Textvarianten (JSON)
  • Wir haben vermutet, dass Formate, die mit wenig Reflection arbeiten (Protobuf) schneller sein müssten als Formate, die intensiv auf Reflection aufbauen
  • Wir haben kein Multithreading im Client verwendet. Außerdem sollten kaum Zugriffe auf Festplatte und Co erfolgen. Deshalb hatten wir die Erwartung, dass die verbrauchte Zeit und die verbrauchte Energie zueinander halbwegs linear sein sollten.
  • Nativ compilierte Anwendungen starten schnell, jedoch gibt es keine weitere Optimierung zur Laufzeit mehr. Hier war unsere Erwartungshaltung, dass Java besser abschneidet.

Nach dem Messen hat uns das folgende Ergebnis erwartet:

Und noch eine Grafik für Daten-Nerds: die einzelnen Messwerte waren teils starken Schwankungen unterworfen. Dies lässt sich teilweise damit erklären, dass z.B. Lüfter verzögert ansprangen. Besonders auffällig ist die Streuweite bei Protobuf. Diese Messung lief jedoch mit Abstand am kürzesten, weshalb sie deutlich weniger Messpunkte enthielt.

Es lässt sich festhalten, dass alle verwendeten Verfahren ungefähr die gleiche Leistung benötigen. Dies deckt sich mit der Vorüberlegung. Es gibt jedoch deutliche Unterschiede in der Laufzeit. Damit ergibt sich auch ein jeweils anderer Energieverbrauch (Leistung x Zeit). Protobuf schneidet hier am besten ab, JSON-Serialisierung mit gson sowie XML bilden in diesem Szenario das Schlusslicht.

Außerdem fällt auf, dass GraalVM hier nicht effizient ist. Auch dies passt zu den Vorüberlegungen: Die Applikation startet sehr schnell, anders als bei der JVM wird der Code während der Laufzeit aber nicht weiter optimiert.

(Noch) nicht getestet haben wir das Feature “profile guided optimizations” von GraalVM. Damit wäre zu erwarten, dass die Performance an die JVM heranreicht. Bei Gelegenheit möchten wir diese Zahlen nachliefern.

Ein Wort zur Wissenschaftlichkeit

An dieser Stelle sei gesagt, dass wir keine Laborgeräte verwendet haben. Wir haben nicht einmal Aussagen über die Messgenauigkeit der verwendeten Steckdose gefunden. Die Ergebnisse sind also mit Vorsicht zu genießen. Außerdem müssen wir aufpassen, welche Aussagen wir generalisieren können. Wir haben nur zwei Frameworks für JSON getestet. Ist es fair, daraus abzuleiten, dass Protobuf grundsätzlich weniger Energie verbraucht? Sicher nicht.

Andererseits passen die gemessenen Werte zu theoretischen Überlegungen: Bei JSON müssen Daten aufwändig in String-Repräsentationen überführt werden. Vieles geschieht mittels Reflection. Bei Protobuf hingegen haben wir es mit einem kompakten Binärformat zu tun, welches viel weniger aufwändig zu schreiben und zu lesen ist und welches mit deutlich weniger Reflection-Nutzung auskommt.

Aufgrund des deutlichen Unterschieds in unseren Messergebnissen zeigen sie eine klare Tendenz. Wir konnten die Zahlen durch mehrfache Ausführung auch gut reproduzieren.

Es ist nicht gesagt, dass andere Hardware (vielleicht auch andere CPU-Architekturen) zu den gleichen Werten kommen. Wir denken allerdings, dass sich das Verhältnis der verwendeten Datenformate untereinander nicht in hohem Maße ändern sollte. Im Zweifel hilft aber nur: selbst nachmessen. Und zwar in einem möglichst realistischen Szenario was Software und Hardware angeht.

Fazit

Basierend auf den Messergebnissen können wir nun Entscheidungen treffen: Welches Datenformat wählen wir? Lohnt sich eine Optimierung überhaupt? Welchen Aufwand wollen wir betreiben, um bessere Ergebnisse zu erzielen?

Trotz einer relativ einfachen Aufgabenstellung und simpler Hardware waren wir in der Lage, den Energieverbrauch von verschiedenen Datenformaten zu vergleichen. Was uns noch mehr freut: mit einem solchen Setup lassen sich auch andere Fragen ähnlich einfach beantworten!

In der Entwicklung von Software sind bei uns Energiemessungen noch kein fester Bestandteil. Wir hoffen aber, dass wir zeigen konnten, dass wir auch mit alltäglichen Entscheidungen einen (kleinen) Unterschied machen können und wir möchten dieses Bewusstsein weiter schärfen. Dazu gehört es auch, die Größe dieses Unterschieds bemessen zu können. Und dafür haben wir nun Werkzeuge geschaffen!