Go
Z Denik
Go je programovací jazyk, který pochází z kuchyně Googlu. Když byl v roce 2009 poprvé představen veřejnosti, tak se hlavně zmiňovalo, že:
- je to staticky typovaný jazyk
- se překládá, tak jako se překládá jazyk C
- přichází s bezpečnou správou paměti pomocí garbage kolektoru
- používá strukturované typy
- má zabudované jazykové konstrukce pro sekvenční komunikaci s ostatními paralelními procesy
Po těch téměř deseti letech od svého uvedení již vyprchalo prvotní nadšení, ale jazyk Go si stále drží dost ze své popularity. Především je podporován a používán velkou firmou. Díky tomu funguje, to co dělá, dělá dobře a zřejmě to tak bude ještě hodně dlouho. Navíc se objevily nové projekty, které na Go staví: Docker je napsán v Go a protože je to nejpopulárnější kontajner, podporuje svým úspěchem i popularitu Go.
Obsah |
Nechte Go plavat!
Go se používá hlavně v systémech, kde je potřeba rychlý start, malé nároky na paměť, snadná komunikace mezi více vlákny a kde se použití klasického Céčka jeví příliš nebezpečné. Go skvěle splňuje funkci systémového jazyka, ale navíc, díky automatické správě paměti, eliminuje již od základu chyby, které se v jiných systémových jazycích dají tak snadno napáchat. Pokud potřebujete jazyk s podobnými vlastnostmi, tak se můžete domnívat, že Go je ta pravá volba. Může být, ale určitě to není jediné možné řešení. Pojďme prozkoumat jednu možnou alternativu: Zkusme použít Javu!
Java!?
Cože? Javu? Toho pomalého, interpretovaného bumbrlíčka, který žere všechnu dostupnou paměť, aby ukojil nároky toho svého nenažraného virtualního stroje? Toho stroje, který se chová téměř jak samostatný operační systém? Tu Javu, kterou každý správný systémák nenávidí? No tak tu zrovna ne. Trochu jinou Javu, ale nejprve se pojďme podívat na vlastnosti Javy jako jazyka:
- je staticky typovaná
- překládá se
- do bajtkódu
- a nebo s pomocí native-image do strojáku, tak jako Céčko
- s bezpečným přístupem ke správě paměti hlavně díky garbage kolektoru
- s objektově orientovaným typy
- s primitivy pro práci s vlákny přímo zabudovanými do jazyka od jeho vzniku
Zní to povědomě? Ano, jazyk Java nabízí ty samé výhody, které jsou připisovány jazyku Go. Když k tomu připočítáme dvacet let soutěžení o nejlepší vývojové prostředí, knihovnu či framework, tak dostaneme výsledek: refaktorování, automatické nápovědy při psaní kódu, podpora rozličných knihoven. To vše vytváří systém, který je vpravdě robustní.
Efektivní spouštění s native-image
Klasický interpret Javy je společně s přidruženým JIT překladačem zatížen nutností udržovat výrazné množstvím meta dat. JIT dokáže generovat vysoce optimalizovaný kód. Někdy optimalizovaný až příliš. Pokud se pak objeví situace, se kterou se při překladu nepočítalo, tak je nutné přepnout zpět do interpretru. Aby toto bylo možné, tak se při běhu udržují megabajty a megabajty meta dat. To není zrovna vhodné pokud člověk cílí na malá zařízení, tak jako Go. Naštěstí však existuje řešení: Pojďme si představit část GraalVM nazvanou native-image.
V principu je native-image AOT překladač Javy a jiných jazyků běžících nad JVM se snadno použitelnou interoperabilitou s [apidesign:C|Céčkem]] a jinými knihovnami operačního systému. native-image dokáže dát programu napsanému v Javě chování, které má Go. To kromě jiného znamená:
- okamžitý start procesu
- žádná zbytečná zátěž metadaty z interpretru a dynamického překladu
- nízké paměťové požadavky
Zahoďte předsudky, že je Java nutně nenažraná. Teď můžeme zkombinovat to nejlepší z Javy (či jiného apidesign:JVM jazyku jako je apidesign:Kotlin či apidesign:Scala) a ahead-of-time překladem native-image, což nám vytvoří ekosystém, který řeší stejné problémy jako Go a dělá to překvapivě dobře!
Co je to vlastně ta rychlost?
Porovnávat rychlost různých jazyků není triviální úkol a dá se při něm velmi dobře podvádět. Na druhou stranu se dá najít sada základních operací, kterou každý Turingově úplný jazyk musí podporovat (if větvení, while cykly, přístup do paměti, alokaci a čištění). Porovnáním těchto operací pak dle mého názoru můžeme změřit Turingovu rychlost jazyků relativně přesně. Přesně o to se pokouším ve svém projektu, který měří rychlost různých jazyků na variantě již antickým Řekům známého algoritmu na výpočet prvočísel.
Zkusme si porovnat Go, Céčko a native-image Javu. Následující výsledky byly získány z https://github.com/jtulach/sieve verze a671eb115 přeložené pomocí go1.9.2 on Ubuntu 16.04:
<source lang="bash"> sieve/go$ ./go | grep Hundred Hundred thousand prime numbers in 253 ms Hundred thousand prime numbers in 263 ms Hundred thousand prime numbers in 261 ms Hundred thousand prime numbers in 270 ms Hundred thousand prime numbers in 250 ms Hundred thousand prime numbers in 277 ms Hundred thousand prime numbers in 241 ms </source>
nyní změňme adresář a zkusme ten samý algoritmus napsaný v Céčku:
<source lang="bash"> sieve/c$ sieve | grep Hundred Hundred thousand prime numbers in 100 ms Hundred thousand prime numbers in 101 ms Hundred thousand prime numbers in 98 ms Hundred thousand prime numbers in 102 ms Hundred thousand prime numbers in 101 ms Hundred thousand prime numbers in 108 ms Hundred thousand prime numbers in 97 ms </source>
A hle: Go je pomalejší než Céčko, což může být daní za bezpečnost jazyka. Prostě mít automatickou správu paměti zřejmě není zcela zadarmo. Teď zkusme ten samý program spustit pomocí native-image:
<source lang="bash"> sieve$ mvn -f java/algorithm/ -Dnative.image=/graalvm/bin/native-image package sieve$ java/algorithm/target/sieve | grep Hundred Hundred thousand primes computed in 184 ms Hundred thousand primes computed in 143 ms Hundred thousand primes computed in 196 ms Hundred thousand primes computed in 222 ms Hundred thousand primes computed in 182 ms Hundred thousand primes computed in 158 ms Hundred thousand primes computed in 150 ms Hundred thousand primes computed in 176 ms Hundred thousand primes computed in 138 ms Hundred thousand primes computed in 140 ms Hundred thousand primes computed in 146 ms Hundred thousand primes computed in 170 ms Hundred thousand primes computed in 156 ms </source>
Opět je tato verze o něco pomalejší než Céčko, ale kupodivu je rychlejší než Go. To je dobrá zpráva: zdá se, že opravdu není nutné Javu odepisovat. Pomocí native-image přeložená Java je rychlostně přinejmenším srovnatelná s Go.