Auf der Suche nach einer leichtgewichtigen cross-kompilierbaren Programmiersprache bin ich auf Go gestoßen. Was dies für Vorteile hat werde ich später erläutern.
Ich arbeite lange und viel mit C#, XAML, PowerShell und trotzdem hat es mir diese low-level Sprache angetan.
Ich nehme euch nun mit auf die Reise und vielleicht wird der eine oder andere sich danach auch mal Go anschauen.
Ich werde hier nicht versuchen dir die Sprache didaktisch in seiner Komplettheit zu erklären. Vielmehr möchte ich Interesse wecken!
Kurze Einführung
Die Sprache wurde 2009 von Google vorgestellt, siehe auch diese Präsentation von Rob Pike. Also ein recht junge Programmiersprache.
Go, also known as Golang, is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. Go is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency.
Einführung aus der englischen Wikipedia, siehe hier.
Maskottchen
Das Maskottchen ist die Taschenratten (engl. Gopher).
Hello World
Hier natürlich das obligatorische Hello World Programm:
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
Wie hat Go meine Aufmerksamkeit gewonnen?
Es gibt zwei Eigenschaften der Sprache Go die für mich ausschlaggebend waren, dieser Sprache eine Chance zu geben. Einmal die Möglichkeit von Cross Compile und die damit verbundene minimale Systemvoraussetzung und die Nebenläufigkeit die seit Geburt von Go ein wichtiger Bestandteil ist.
Cross Compile
Diese 24 Zeilen Powershell kompilieren einen funktionierenden WebServer für die Plattformen Windows, Linux, Linux auf ARM und macOS. D.h. ich kann auf meinem Windows-Rechner für meinen Raspberry Pi entwickeln und meine einzige Voraussetzung bei Linux/ARMv5 ist ein Kernel größer gleich 3.1.
Mehr zu den Systemvoraussetzung hier: https://github.com/golang/go/wiki/MinimumRequirements.
Nebenläufigkeit
Diese Beispiel zeigt wir man mit 5.000 nebenläufigen Berechnungen Pi ausrechnet. Die Kommunkation zwischen „goroutine“, dem Konstrukt zur Nebenläufigkeit, verläuft ausschließlich über Channels, also nicht wie bei Java, C++, C# über shared memory.
Do not communicate by sharing memory; instead, share memory by communicating.
https://blog.golang.org/share-memory-by-communicating
In Go wird für eine goroutine 2KB reserviert, wobei für einen Thread bei win32 1MB benötigt.
Der Vergleich hinkt ein wenig, da goroutines keine Threads sind. Da diese nur in virtuellen Speicher der Runtime existieren. Unter C# entspricht ein Thread auch einem Thread im Betriebssystem. Unter Go werden goroutines auf Threads verteilt und verwaltet.
Achtung! Nebenläufigkeit ist nicht Parallelisierung, siehe dazu auch Rob Pike – ‘Concurrency Is Not Parallelism’.
Was macht Go aus?
Professor der Informatik auf der KTH
Ich fand die Ausführung von Stefan Nilsson, Professor auf der KTH Royal Institute of Technology in Stockholm, Schweden zum Thema sehr zutreffend.
Er gliedert seine Ausführung „Why Go? – Key advantages you may have overlooked“ wie folgt.
- Minimalismus
- Code Transparenz
- Kompatibilität
- Performance
Sehr kurz wiedergegeben schreibt er:
Minimalismus: Go ist eine minimalistische Sprache. Die Spezifikation der Programmiersprache Go umfasst 50 Seiten (Java dagegen 750 Seiten) inklusive zahlreicher Beispiele. Ein erfahrener Entwickler kann nur anhand dieser Spezifikation einige über die Sprache lernen.
Code Transparenz: Um eine einfache Verständlichkeit des Codes zu erreichen gibt eine vorgegebene Formatierung, welche mit dem ausgelieferten Tool fmt angewendet werden kann. Nicht verwendete Pakete oder Variablen werden zur Kompilationszeit einen Fehler aufwerfen.
Kompatibilität: Es ist garantiert, dass die Major-Version von Go, aktuell 1, immer Rückwärtskompatibel bleibt. Dies hat sich auch in der Vergangenheit behauptet. Dazu ist das das gesamte Go-Projekt open source.
Performance: Go ist eine kompilierbare Sprache, dessen Kompilat aus einer einzigen portablen Datei besteht. Es gibt keine Abhängigkeiten zu VMs oder libs. Es gibt ein Cross-Compile zu den üblichen Betriebssystemen und Prozessorarchitekturen. Die zu erwartende Ausführungsgeschwindigkeit entspricht C++ oder Java. Ein Garbage Collector schützt gegen Memory Leaks, der GC hat eine sehr kleine Latenz, so dass dieser GC-Thread nicht auffallen sollte. Die Standard-Pakete sind von hoher Qualität und optimiert auf Geschwindigkeit, als positives Beispiel wird hier Regex erwähnt. Zum Schluss kann man noch sagen, die Kompilationszeit in Go ist gut und skaliert gut mit großen Projekten.
Engineering Manager bei Google
In Blogartikel „400 DAYS OF GO“ schreibt Philip O’Toole über die Produktivität von Go. Seine Aussagen versuche ich hier gekürzt wiederzugeben.
- Go braucht keine IDE, es reicht ein einfacher Texteditor.
- Die Kompilierungszeit ist sehr schnell, dass gesamte Projekt influxdb kann in 3 Sekunden erstellt werden. Entwickler die Erfahrung mit kompilieren und linken von C++ Code haben, kennen ganz andere Zeiten.
- Die Standard-Bibliotheken sind sehr umfangreich und von hoher Qualität, ergänzend kann man sagen ähnliche wie bei der .NET Runtime.
- Eingebaute Datenstrukturen wir Hash Tables und Dynamic Arrays
- Die Formatierung ist vorgegeben, mit der Go SDK wird das Tool go fmt zum formatieren ausgeliefert
- Es wird EINE statisch gelinkte ausführbare Datei kompiliert, alle Abhängigkeiten sind inkludiert. Unter Linux wird z.B. nur ein Kernel ab Version 2.6.23 (2007) benötigt
- Es gibt ein integriertes Test Framework, dadurch sind die Tests immer konsistent und der Entwickler muss sich nicht mit Third-Party Testframworks auseinandersetzen.
- Integrierte Performance Analyse Tools helfen beim Identifizieren von CPU und Memory Probleme.
Aber er scheibt auch über Dinge die nicht perfekt sind:
- Garbage Collection
- Boilerplate
- GOPATH
Noch ein bisschen Motivation…
Im Golang Repository auf GitHub ist eine interessante Sammlung von Erfolgsberichten zusammengestellt, siehe hier https://github.com/golang/go/wiki/SuccessStories. Diese Liste beinhaltet Beiträge u.a. von CloudFlare (1, 2), soundcloud.com (1), NTP Pool Project (1), bitly.com (1) und vielen mehr.
Im Golang Repository ist auch eine Aufführung von Entwicklern und Firmen die ausführlich erklären, wieso sie Go verwenden, siehe dazu hier https://github.com/golang/go/wiki/WhyGo.
Spannenden Projekte in Go geschrieben:
- Docker / Kubernetes
- CIRCL: An Advanced Cryptographic Library
- IPFS implementation in Go
- Go Implementation of WireGuard
- Caddy: Fast, cross-platform HTTP/2 web server with automatic HTTPS
Wie kann ich Go lernen?
Es gibt einen sehr große Anzahl von Möglichkeiten um mit Go zu starten. Für mich hat der Wechsel von Go by Example und der Spezifikation gut funktioniert. Gefolgt vom Einlesen in Verschiedenen open source Projekten.
Es gibt unzählige Möglichkeiten einen Einstieg zu finden, hier ist z.B. eine Liste aus dem Go GitHub Projekt https://github.com/golang/go/wiki/Learn.
Hello World erklären
Schauen wir uns nochmal das Hello World Programm an:
package main // 1
import "fmt" // 2
func main() { // 3
fmt.Println("hello world") // 4
}
Diese minimale Programm lässt sich wie folgt aufschlüsseln:
- Jede Go-Datei muss mit
package name
beginnen. - Mit
import
werden Pakete eingebunden. - Die Funktion
func main()
ist reserviert als Einstiegspunkt. - Mit
fmt.
greifen wir auf das importierte Paket zu und rufenfmt.Println
mit FunktionPrintln
auf.
Jedes Paket aus der Standard-Bibliothek ist dokumentiert, zufmt.Println
siehe hier: https://golang.org/pkg/fmt/#Println
Als Parameter wird die Zeichenketten“Hello World”
übergeben.
Die geschweiften Klammern legen den Start und das Ende der Funktion main fest
Technische Eindrücke
Tooling
Für die ersten Go-Anwendung ist nur eine Sache notwendig, das GO SDK, siehe hier https://golang.org/dl/.
Eine IDE ist trotzdem zum empfehlen, hier kann z.B. VS Code oder das kostenpflichtige JetBrains GoLand verwendet werden.
Compile
Ein einfaches kompilieren wird einfach mit dem Argument build
gestartet: go build -o webservice.exe webservice.go
Die Parametrisierung erfolgt über Umgebungsvariablen oder Linker Flags.
Über Umgebungsvariablen kann z.B. das Zielsystem und Prozessorarchitektur festgelegt werden:
$env:GOOS = ‚windows‘; $env:GOARCH = ‚amd64‘; // Windows x64
$env:GOOS = ‚linux‘; $env:GOARCH = ‚amd64‘; // Linux x64
$env:GOOS = ‚linux‘; $env:GOARCH = ‚arm64‘; // Arm x64
$env:GOOS = ‚darwin‘; $env:GOARCH = ‚amd64‘; // macOS x64
Mit Linker Flags können z.B. die Debug-Symbols entfernt werden go build -ldflags "-s -w "
, eine Variable im Code gesetzt werden -ldflags="-X 'main.Version=v1.0.0'"
oder der Konsolenhost deaktiviert werden -ldflags "-H windowsgui"
.
Cross Compile
Ein Cross Compile gehört zu den sehr nützlichen Eigenschaften von Go.
Hier kann die Anwendung unter webservice.go einfach für 4 Plattformen gebaut werden:
$env:GOOS = 'windows'; $env:GOARCH = 'amd64'; go build -o webservice_demo.exe webservice.go
$env:GOOS = 'linux'; $env:GOARCH = 'amd64'; go build -o webservice_demo_linux webservice.go
$env:GOOS = 'linux'; $env:GOARCH = 'arm64'; go build -o webservice_demo_arm webservice.go
$env:GOOS = 'darwin'; $env:GOARCH = 'amd64'; go build -o webservice_demo_mac webservice.go
Die komplette Liste der GOOS und GOARCH hier hier zu finden: https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63
Optimierung der Dateigröße
Ein Go-Kompilat ist leider nicht sehr klein, die oben gezeigt HelloWorld-Anwendung ist ganze 1.056 KB groß.
Wieso so große ausführbare Dateien? In dieser Datei ist nicht nur die Anweisung fmt.Println("hello world")
enthalten, sondern auch die Runtime mit u.a. dem garbage collector, concurrency, stack management. Das Ziel an die Ausführung ist laut den Go Entwicklern „latency, ease of deployment, precise garbage collection, fast startup time, performance.“ Mit Optimierung in Go 1.7 wurde die Dateigröße auf bis zu 20% reduziert.
Diese Dateigröße lässt sich jedoch noch weiter reduzieren. Mit den Linker Flags entfernen wir die Debug Symbole und kommen damit auf 750 KB. Nun können wir via UPX das Kompilat sogar noch komprimieren wenn es das Einsatzszenario zulässt, so kommen wir dann sogar auf 242 KB.
Durch die Verwendung von UPX wird die Startup Time verlängert und eventuell die Speicherauslastung erhöht. Bei einem extrem kleinen System muss dies berücksichtigt werden.
Eigene Anwendungsbereiche
- Sync nach Azure auf der Homematic Smart Home Zentrale CCU3 (ARM64)
- Synchronisation von GitLab-Files mit dem Dateisystem (GitLabFileDownloader)
- Analysetool für Systemintegratoren
- Verwendung einer Post-quantum Cryptography Bibliothek
- Kapseln von .NET Core Anwendungen
- Temperatursensor mit Upload nach Azure (ARM64 auf einem Raspberry PI Nano)
Weiterführende Informationen
- Create the smallest and secured golang docker image based on scratch
https://medium.com/@chemidy/create-the-smallest-and-secured-golang-docker-image-based-on-scratch-4752223b7324 - 10 Top Golang Tutorials to Learn Go Programming Online
https://medium.com/quick-code/top-online-courses-to-learn-go-programming-language-golang-for-beginners-c228c615946c - Go cheatsheet
https://devhints.io/go - LearnConcurrency
https://github.com/golang/go/wiki/LearnConcurrency - Five things that make Go fast
https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast - Effective Go
https://golang.org/doc/effective_go.html - Using ldflags to Set Version Information for Go Applications
https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications - Ein paar Gists zu diesem Beitrag
https://gist.github.com/dhcgn/be188d6809c773dc873a184e4ac06959 - GopherCon 2017: Edward Muller – Go Anti-Patterns
https://youtu.be/ltqV6pDKZD8 - Smaller Go 1.7 binaries
https://blog.golang.org/go1.7-binary-size - Go After 2 Years in Production
https://blog.iron.io/go-after-2-years-in-production/