TinyGo: alternativní překladač a runtime systém programovacího jazyka Go

Dnes
Doba čtení: 28 minut

Sdílet

Logo programovacího jazyka Go
Autor: Go
Většina programátorů vytvářejících aplikace v Go používá překladač, který je součástí standardní instalace Go. Ovšem existují i alternativy, mezi než patří gccgo a TinyGo. Dnes se seznámíme se základními vlastnostmi TinyGo.

Obsah

1. TinyGo: alternativní překladač a runtime systém programovacího jazyka Go

2. Základní vlastnosti standardního překladače Go

3. TinyGo v porovnání se standardním překladačem Go

4. Instalace TinyGo z dostupných balíčků

5. Instalace TinyGo ze zdrojových kódů

6. Podporované cílové platformy

7. Čas překladu a velikost výsledného souboru pro nejjednodušší variantu programu typu „Hello world“

8. Program typu „Hello world“ volající funkci fmt.Println

9. Ovlivnění velikosti výsledného spustitelného souboru přepínači TinyGo

10. Porovnání velikosti výsledných souborů při použití různých přepínačů

11. Benchmark pro zjištění rychlosti programů přeložených standardním překladačem Go a TinyGo

12. Výsledky změřené pro standardní překladač Go

13. Optimalizace na rychlost v TinyGo?

14. Porovnání změřených výsledků

15. Druhá varianta benchmarku: výpočet jednotlivých řádků v samostatných gorutinách

16. Porovnání změřených výsledků benchmarků

17. Překlad do WebAssembly

18. Závěr

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. TinyGo: alternativní překladač a runtime systém programovacího jazyka Go

Programovací jazyky je možné rozdělovat do kategorií na základě různých kritérií. Typicky mezi tato kritéria patří rozdělení mezi interpretry a překladače (s tímto rozdělením se dříve či později setká prakticky každý vývojář), ovšem jazyky můžeme taktéž rozdělovat podle jejich typového systému, podporovaného paradigmatu (imperativní, funkcionální, objektově orientované, logické atd.).

Ovšem existuje ještě jedno poměrně důležité kritérium, které rozlišuje mezi programovacími jazyky, které jsou specifikovány či možná lépe řečeno definovány přímo svojí implementací (typickým zástupcem těchto jazyků je Perl) a jazyky s vlastní více či méně formální specifikací. Pro jazyky, které patří do druhé zmíněné kategorie, pak většinou existuje hned několik jejich implementací. Příkladů je celá řada: C, C++, Java, Python, stovky implementací LISPu a Scheme atd. atd.

Mezi programovací jazyky, které mají vlastní specifikaci (a nejsou tedy definovány implementací) patří i programovací jazyk Go. Většina vývojářů, kteří tento jazyk používají pro tvorbu aplikací, většinou pracuje se standardním překladačem tohoto jazyka (někdy se tento překladač nazývá gc, i když to může být poměrně matoucí zkratka), ovšem ve skutečnosti existují i další (alternativní) překladače jazyka Go. Mezi tyto překladače patří především gccgo a TinyGo. V dnešním článku se zaměříme právě na popis překladače TinyGo a pokusíme se porovnat jeho základní vlastnosti se standardním překladačem jazyka Go dostupným na stránkách go.dev/dl.

Poznámka: ve skutečnosti nebudeme porovnávat pouze dva překladače programovacího jazyka Go, ale současně i jejich runtime, protože ten je v Go poměrně důležitý (už jen kvůli existenci automatické správy paměti, gorutin a kanálů). Runtime totiž ovlivňuje velikosti a částečně i rychlosti výsledných kódů.

2. Základní vlastnosti standardního překladače Go

Na tomto místě je možná dobré si položit otázku, z jakého důvodu vlastně vůbec vznikl překladač TinyGo? Pokusme se nejdříve shrnout základní vlastnosti standardního překladače (a runtime systému) jazyka Go. Standardní překladač je znám především vysokou, v některých případech až skoro neuvěřitelnou, rychlostí překladu, což tvůrcům Go umožnilo oprostit se od konceptu odděleného překladu částí kódu do objektových souborů s jejich pozdějším slinkováním (což jsou vlastně pouze triky, které byly kdysi vymyšleny pro pomalé překladače resp. pro platformy s malým množstvím operační paměti, které si rozdělení překladu vynutily).

Programátoři, co nesnášíte BS, ale máte rádi business! Y Soft je česká firma s globálním dopadem (100+ zemí, 1M+ uživatelů a >100% meziroční růst). R&D úplně bez manažerů (130 developerů). Otevíráme 30 pozic pro Cloud a AI: Praha/Brno/Ostrava/remote. Zodpovědnost ano, mikro-management ne. Pojď někam, kde můžeš věci změnit.

Y Soft logo

Nicméně standardní překladač Go je velmi rychlý a byl vlastně vytvořen na „zelené louce“ (není založen na LLVM ani na žádném jiném podobném univerzálním nástroji). Rychlost překladu ovšem současně znamená, že překladač neprovádí žádné sofistikovanější optimalizace, které by vyžadovaly poměrně mnoho strojového času (ovšem jak uvidíme dále, je standardní překladač Go až překvapivě dobrý).

Současně standardní překladač jazyka Go oficiálně podporuje relativně malé množství cílových platforem. Typicky se jedná o 32bitové a 64bitové platformy (viz též https://gist.github.com/a­sukakenji/f15ba7e588ac42795f421­b48b8aede63, přičemž přidání podpory pro další cílové platformy je složité (vše se musí implementovat, nelze se spolehnout například na již výše zmíněný LLVM atd.). Výsledné binární soubory jsou staticky slinkované a jsou poměrně velké, což sice pro typické způsoby nasazení nepředstavuje velký problém (aplikace běžící na serveru), ovšem znamená to, že Go v této podobě není dobře využitelné na mikrořadičích s menší paměťovou kapacitou pro uložení výsledného spustitelného binárního obsahu.

Poznámka: v benchmarcích použijeme tuto verzi Go:
$ go version
 
go version go1.24.10 linux/amd64

3. TinyGo v porovnání se standardním překladačem Go

Zatímco je standardní překladač jazyka Go optimalizován s ohledem na rychlost překladu, má TinyGo odlišné cíle. Jedná se především o snahu o zmenšení velikosti výsledných binárních souborů (to souvisí se změnami v runtime systému TinyGo a s konfigurovatelností tohoto runtime) a taktéž o podporu mnohem většího množství cílových platforem, pro které je možné aplikace přeložit. Mezi podporované platformy pochopitelně patří mainstreamové x86 (a samozřejmě x86–64) a ARM (AArch64), ale i mnoho různých méně výkonných mikrořadičů (viz též šestou kapitolu). A zapomenout nesmíme ještě na jednu platformu – WebAssembly (o které právě paralelně vychází samostatný seriál). Kombinace WebAssembly + malé velikosti výsledných binárních souborů je pro spouštění aplikací v rámci webových prohlížečů velmi užitečná, takže se TinyGo v této oblasti vývoje stává velmi oblíbenou alternativou.

Poznámka: standardní překladač Go sice WebAssembly podporuje také, ale výsledné WASM soubory jsou dosti velké. To si ostatně taktéž ověříme.

TinyGo je navíc postaveno nad LLVM, což znamená, že je umožněno provádění i poměrně komplikovaných optimalizací (jak na výslednou velikost, tak i na rychlost). Ovšem za tuto možnost platíme delší dobou překladu (mnohdy i několikanásobnou). Runtime systém TinyGo je, na rozdíl od standardního Go, do značné míry konfigurovatelný. Například je možné pro krátkodobě běžící skripty nebo naopak pro aplikace, které mají běžet na mikrořadičích, z runtime zcela odstranit automatického správce paměti atd. Se všemi těmito koncepty se seznámíme v praktické části dnešního článku.

Poznámka: ovšem tvrzení, že TinyGo postavené nad LLVM provádí lepší optimalizace, než standardní Go, je nutné dokázat, takže si toto tvrzení ověříme na benchmarcích.

4. Instalace TinyGo z dostupných balíčků

Instalace TinyGo může (minimálně v Linuxu) probíhat několika možnými způsoby. Některé distribuce Linuxu obsahují balíčky s TinyGo, takže v tomto případě může být instalace snadná. Příkladem mohou být distribuce postavené na DNF/YUM, z nichž některé dokážou chybějící balíček (resp. přesněji řečeno chybějící příkaz) doinstalovat:

$ tinygo
 
bash: tinygo: command not found...
Install package 'tinygo' to provide command 'tinygo'? [N/y] y

Pokud na předchozí otázku odpovíme „y“, TinyGo se začne instalovat:

 * Waiting in queue...
 * Loading list of packages....
The following packages have to be installed:
 clang17-libs-17.0.6-9.fc40.x86_64      Runtime library for clang
 clang17-resource-filesystem-17.0.6-9.fc40.x86_64       Filesystem package that owns the clang resource directory
 llvm17-libs-17.0.6-7.fc40.x86_64       LLVM shared libraries
 qemu-system-arm-core-2:8.2.9-1.fc40.x86_64     QEMU system emulator for ARM
 qemu-system-riscv-core-2:8.2.9-1.fc40.x86_64   QEMU system emulator for RISC-V
 qemu-user-2:8.2.9-1.fc40.x86_64        QEMU user mode emulation of qemu targets
 tinygo-0.30.0-4.fc40.x86_64    Go compiler for small places
Proceed with changes? [N/y] y

Potvrzení, že skutečně chceme doinstalovat i závislé balíčky (popravdě řečeno jsou závislosti na QEMU poněkud podezřelé, ale budiž):

 * Waiting in queue...
 * Waiting for authentication...
 * Waiting in queue...
 * Downloading packages...
 * Requesting data...
 * Testing changes...
 * Installing packages...

Po provedení předchozích kroků by mělo být možné spustit příkaz tinygo:

$ tinygo

Ovšem důležité je zkontrolovat nainstalovanou verzi. V tomto případě byla doinstalována starší verze 0.30.0, která je sice plně funkční, ale hodnoty, které získáme v dalších kapitolách, zde mohou být horší:

No command-line arguments supplied.
TinyGo is a Go compiler for small places.
version: 0.30.0
usage: tinygo <command> [arguments]
 
commands:
  build:   compile packages and dependencies
  run:     compile and run immediately
  test:    test packages
  flash:   compile and flash to the device
  gdb:     run/flash and immediately enter GDB
  lldb:    run/flash and immediately enter LLDB
  monitor: open communication port
  env:     list environment variables used during build
  list:    run go list using the TinyGo root
  clean:   empty cache directory (/home/ptisnovs/.cache/tinygo)
  targets: list targets
  info:    show info for specified target
  version: show version
  help:    print this help text
 
for more details, see https://tinygo.org/docs/reference/usage/

Na novějších systémech dojde k instalaci verze 0.39.0, což je poslední stabilní verze TinyGo:

$ tinygo
 
No command-line arguments supplied.
TinyGo is a Go compiler for small places.
version: 0.39.0
usage: tinygo <command> [arguments]
commands:
                build:          compile packages and dependencies
                run:            compile and run immediately
                test:           test packages
                flash:          compile and flash to the device
                gdb:            run/flash and immediately enter GDB
                lldb:           run/flash and immediately enter LLDB
                monitor:        open communication port
                ports:          list available serial ports
                env:            list environment variables used during build
                list:           run go list using the TinyGo root
                clean:          empty cache directory (/home/ptisnovs/.cache/tinygo)
                targets:        list targets
                info:           show info for specified target
                version:        show version
                help:           print this help text
for more details, see https://tinygo.org/docs/reference/usage/

Poslední (již přeloženou) verzi TinyGo určenou pro vývojáře TinyGo, ale využitelnou i pro jiné účely, je možné získat z adresy https://github.com/tinygo-org/tinygo/actions/workflow­s/linux.yml?query=branch%3A­dev. Klikněte na jméno posledního sestavení (buildu) a stáhněte si artefakt pro vaši platformu.

5. Instalace TinyGo ze zdrojových kódů

Alternativně je pochopitelně možné si TinyGo nainstalovat přímo ze zdrojových kódů, což je snadné, ovšem musíte mít nainstalovány všechny potřebné nástroje. Kromě běžných nástrojů typu Make a Git je nutné doinstalovat i nástroj ninja (ninja-build) a vyžadováno je i původní Go.

Naklonování repositáře se zdrojovými kódy TinyGo:

$ git clone https://github.com/tinygo-org/tinygo.git
 
Cloning into 'tinygo'...
remote: Enumerating objects: 36613, done.
remote: Counting objects: 100% (1255/1255), done.
remote: Compressing objects: 100% (702/702), done.
remote: Total 36613 (delta 915), reused 565 (delta 553), pack-reused 35358 (from 4)
Receiving objects: 100% (36613/36613), 12.70 MiB | 8.92 MiB/s, done.
Resolving deltas: 100% (26501/26501), done.

Po spuštění překladu příkazem make se může vypsat, že je nejdříve nutné přeložit samotné LLVM. To se provede dvěma kroky:

$ make llvm-source
 
git clone -b xtensa_release_19.1.2 --depth=1 https://github.com/espressif/llvm-project llvm-project
Cloning into 'llvm-project'...
remote: Enumerating objects: 153857, done.
remote: Counting objects: 100% (153857/153857), done.
remote: Compressing objects: 100% (124651/124651), done.
...
...
...

Následovaným krokem:

$ make llvm-build
 
(tento krok pochopitelně trvá poměrně dlouhou dobu, konkrétně se překládá více
než 3000 zdrojových kódů)

A konečně spustíme závěrečnou fázi překladu, která je již rychlá:

$ make
 
go: downloading go.bug.st/serial v1.6.0
go: downloading github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf
go: downloading golang.org/x/tools v0.30.0
go: downloading github.com/mattn/go-colorable v0.1.13
go: downloading github.com/mattn/go-tty v0.0.4
go: downloading github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
go: downloading tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74
go: downloading github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
go: downloading github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
go: downloading github.com/gofrs/flock v0.8.1
go: downloading github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
go: downloading github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
go: downloading github.com/mattn/go-isatty v0.0.20
go: downloading golang.org/x/sys v0.30.0
go: downloading github.com/creack/goselect v0.1.2

Výsledek je uložen v podadresáři build:

$ ls -l build
 
total 173972
-rwxr-xr-x. 1 ptisnovs ptisnovs 178143696 Dec  7 15:19 tinygo

Ověříme si verzi TinyGo, kterou jsme získali překladem:

$ ./tinygo version
 
tinygo version 0.39.0 linux/amd64 (using go version go1.24.10 and LLVM version 19.1.2)

6. Podporované cílové platformy

Jak jsme si již napsali v úvodních kapitolách, patří mezi jednu z klíčových vlastností překladače TinyGo podpora pro překlad aplikací na mnoho cílových platforem (architektur CPU či MCU), a to včetně mnoha typů populárních mikrořadičů. Všechny aktuálně podporované cílové platformy je možné zjistit po zadání příkazu:

$ tinygo targets

TinyGo verze 0.39.0 vypíše tyto platformy (povšimněte si použitých jmenných konvencí):

adafruit-esp32-feather-v2
ae-rp2040
arduino
arduino-leonardo
arduino-mega1280
arduino-mega2560
arduino-mkr1000
arduino-mkrwifi1010
arduino-nano
arduino-nano-new
arduino-nano33
arduino-zero
atmega1284p
atsame54-xpro
attiny1616
badger2040
badger2040-w
bluemicro840
bluepill
bluepill-clone
btt-skr-pico
challenger-rp2040
circuitplay-bluefruit
circuitplay-express
clue
clue-alpha
cortex-m-qemu
d1mini
digispark
elecrow-rp2040
elecrow-rp2350
esp-c3-32s-kit
esp32
esp32-c3-devkit-rust-1
esp32-coreboard-v2
esp32-mini32
esp32c3
esp32c3-12f
esp32c3-supermini
esp8266
feather-m0
feather-m0-express
feather-m4
feather-m4-can
feather-nrf52840
feather-nrf52840-sense
feather-rp2040
feather-stm32f405
gameboy-advance
gemma-m0
gnse
gobadge
gopher-badge
gopherbot
gopherbot2
grandcentral-m4
hifive1b
hw-651
hw-651-s110v8
itsybitsy-m0
itsybitsy-m4
itsybitsy-nrf52840
kb2040
lgt92
lorae5
m5paper
m5stack
m5stack-core2
m5stamp-c3
m5stick-c
macropad-rp2040
maixbit
makerfabs-esp32c3spi35
matrixportal-m4
mch2022
mdbt50qrx-uf2
metro-m4-airlift
metro-rp2350
microbit
microbit-s110v8
microbit-v2
microbit-v2-s113v7
microbit-v2-s140v7
mksnanov3
nano-33-ble
nano-33-ble-s140v6-uf2
nano-33-ble-s140v7
nano-33-ble-s140v7-uf2
nano-rp2040
nicenano
nodemcu
nrf52840-mdk
nrf52840-mdk-usb-dongle
nrf52840-s140v6-uf2
nrf52840-s140v6-uf2-generic
nrf52840-s140v7-uf2
nucleo-f103rb
nucleo-f722ze
nucleo-l031k6
nucleo-l432kc
nucleo-l476rg
nucleo-l552ze
nucleo-wl55jc
p1am-100
particle-3rd-gen
particle-argon
particle-boron
particle-xenon
pca10031
pca10040
pca10040-s132v6
pca10056
pca10056-s140v6-uf2
pca10056-s140v7
pca10059
pca10059-s140v7
pga2350
pico
pico-plus2
pico-w
pico2
pico2-w
pinetime
pybadge
pygamer
pyportal
qtpy
qtpy-esp32c3
qtpy-rp2040
rak4631
reelboard
reelboard-s140v7
riscv-qemu
rp2040
rp2350
rp2350b
simavr
stm32f469disco
stm32f4disco
stm32f4disco-1
stm32l0x1
swan
teensy36
teensy40
teensy41
thingplus-rp2040
thumby
tiny2350
tkey
trinket-m0
trinkey-qt2040
tufty2040
wasi
wasip1
wasip2
wasm
wasm-unknown
waveshare-rp2040-tiny
waveshare-rp2040-zero
wioterminal
x9pro
xiao
xiao-ble
xiao-esp32c3
xiao-rp2040
Poznámka: mezi podporované platformy patří i WebAssembly (WASM), což je další důvod, proč je TinyGo v současnosti populární.

7. Čas překladu a velikost výsledného souboru pro nejjednodušší variantu programu typu „Hello world“

V praktické části článku se pokusíme o porovnání TinyGo se standardním překladačem jazyka Go (a s jeho runtime systémem). Začneme, jak je po vydání původní knihy o jazyku C dobrým zvykem, programem typu „Hello, world!“. Tento program lze v Go implementovat tak, že není nutné volat žádnou knihovní funkci; namísto toho můžeme zavolat standardní (ovšem v praxi spíše obcházenou) funkci println:

$ go doc builtin.println
 
package builtin // import "builtin"
 
func println(args ...Type)
    The println built-in function formats its arguments in an
    implementation-specific way and writes the result to standard error.
    Spaces are always added between arguments and a newline is appended. Println
    is useful for bootstrapping and debugging; it is not guaranteed to stay in
    the language.

Implementace programu bude vypadat následovně:

package main
 
func main() {
        println("Go!")
}

Porovnejme nyní velikosti spustitelných souborů, které vzniknou překladem.

Standardní Go:

$ stat hello
  File: hello
  Size: 1519795         Blocks: 2976       IO Block: 4096   regular file
Device: 0,44    Inode: 657113      Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (14282/ptisnovs)   Gid: (14282/ptisnovs)
Context: unconfined_u:object_r:user_tmp_t:s0
Access: 2025-11-28 16:32:51.751457255 +0100
Modify: 2025-11-28 16:32:51.695457298 +0100
Change: 2025-11-28 16:32:51.695457298 +0100
 Birth: 2025-11-28 16:32:51.694626885 +0100

TinyGo:

$ stat hello
  File: hello
  Size: 542536          Blocks: 1064       IO Block: 4096   regular file
Device: 0,44    Inode: 657114      Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (14282/ptisnovs)   Gid: (14282/ptisnovs)
Context: unconfined_u:object_r:user_tmp_t:s0
Access: 2025-11-28 16:35:03.068357976 +0100
Modify: 2025-11-28 16:35:02.877358121 +0100
Change: 2025-11-28 16:35:02.877358121 +0100
 Birth: 2025-11-28 16:35:02.877358121 +0100

V tomto případě tedy TinyGo vytvořilo soubor o zhruba třetinové velikosti.

TinyGo

Obrázek 1: Porovnání velikosti výsledných binárních spustitelných souborů. 

Autor: tisnik, podle licence: Rights Managed

Odlišné budou i časy překladu:

$ time go build hello.go
 
real    0m0.161s
user    0m0.232s
sys     0m0.131s
$ time tinygo build hello.go
 
real    0m0.441s
user    0m0.489s
sys     0m0.190s

Ovšem toto měření nebude zcela přesné, takže zkusme překlad několikrát opakovat a porovnat průměrné časy:

for i in `seq 10`
do
    /usr/bin/time -f %E -o hello_go.txt -a go build hello.go
done
 
for i in `seq 10`
do
    /usr/bin/time -f %E -o hello_tinygo.txt -a ./tinygo build hello.go
done

Změřené výsledky pro standardní Go:

0:00.14
0:00.06
0:00.06
0:00.05
0:00.06
0:00.05
0:00.05
0:00.06
0:00.05
0:00.06

Změřené výsledky pro TinyGo:

0:00.45
0:00.45
0:00.45
0:00.44
0:00.44
0:00.50
0:00.45
0:00.54
0:00.44
0:00.47
Poznámka: v tomto případě je tedy překlad pomocí TinyGo zhruba sedmkrát pomalejší!
TinyGo

Obrázek 2: Porovnání rychlostí překladu. 

Autor: tisnik, podle licence: Rights Managed

8. Program typu „Hello world“ volající funkci fmt.Println

Nyní zdrojové kódy programu typu „Hello world“ upravíme, a to konkrétně takovým způsobem, že se namísto standardní funkce println bude volat funkce Println z balíčku fmt:

$ go doc fmt.Println
 
package fmt // import "fmt"
 
func Println(a ...any) (n int, err error)
    Println formats using the default formats for its operands and writes to
    standard output. Spaces are always added between operands and a newline
    is appended. It returns the number of bytes written and any write error
    encountered.

Tato relativně malá úprava povede k tomu, že se do výsledného binárního souboru přidá i část knihovny fmt a současně i její závislosti:

package main
 
import "fmt"
 
func main() {
        fmt.Println("Go!")
}

Opět si nejdříve porovnejme velikosti výsledných souborů, které vzniknou překladem tohoto demonstračního příkladu:

Překladač Velikost
std. Go 2130697
TinyGo 1074968
TinyGo

Obrázek 3: Porovnání velikosti výsledných binárních spustitelných souborů. 

Autor: tisnik, podle licence: Rights Managed
Poznámka: TinyGo stále generuje mnohem menší soubory, a to i v případě, že bylo nutné do runtime přidat i celou další knihovnu.

Časy překladu výsledné binární spustitelné aplikace.

Standardní překladač jazyka Go:

0:00.20
0:00.08
0:00.09
0:00.09
0:00.08
0:00.07
0:00.08
0:00.07
0:00.08
0:00.07

TinyGo:

0:03.14
0:03.18
0:03.06
0:03.06
0:03.18
0:03.06
0:03.03
0:03.21
0:03.01
0:03.12

Grafické porovnání rychlostí překladu:

TinyGo

Obrázek 4: Porovnání rychlostí překladu. 

Autor: tisnik, podle licence: Rights Managed
Poznámka: nyní je standardní Go opět rychlejší, ovšem více než tricetkrát!

9. Ovlivnění velikosti výsledného spustitelného souboru přepínači TinyGo

Způsob překladu prováděný překladačem TinyGo je možné (na rozdíl od standardního Go) ovlivnit mnoha přepínači. Těmi lze řídit jak to, jestli má být kód optimalizován na rychlost nebo na velikost, ale například je možné odstranit ladicí symboly, změnit plánovač (scheduler) použitý pro řízení a přepínání gorutin, ale například i zvolit to, jaký se má použít automatický správce paměti. Zajímavé je, že automatický správce paměti může být z výsledného runtime zcela odstraněn. Na první pohled se sice může jednat o nelogické rozhodnutí, protože jazyk Go je na správci paměti postaven (paměť nelze uvolňovat explicitně), ovšem pro krátkodobě běžící nástroje nebo například pro programy, které mají běžet na MCU (v nichž se žádná další paměť mnohdy nealokuje), může být tato volba užitečná, neboť dokáže poměrně razantním způsobem snížit velikost runtime subsystému, který je součástí každého přeloženého programu.

Volby překladače, které ovlivňují rychlost výsledného programu a velikost vygenerovaného souboru, jsou vypsány v následující tabulce:

Přepínač Stručný popis
-no-debug odstranění ladicích informací
-nobounds odstranění kódu pro kontrolu překročení mezí
-gc volba, která implementace správce paměti se má použít (lze zvolit i none nebo leaking)
-scheduler volba, který plánovač se má použít (lze zvolit i none)
-opt 0–2 optimalizace na rychlost
-opt s optimalizace na velikost

10. Porovnání velikosti výsledných souborů při použití různých přepínačů

Vyzkoušejme si, jak se použití přepínačů zmíněných v předchozí kapitole projeví na velikosti výsledných spustitelných binárních souborů získaných překladem. Začneme první variantou programu typu „Hello world“, tedy variantou volající standardní funkci println:

Způsob překladu Velikost
std.Go 1519795
std.TinyGo 542536
-no-debug 79584
-scheduler=none 73616
-gc=leaking 14464
Poznámka: odstraněním automatické správy paměti se velikost souboru zmenšila na pouhých 14kB, což je srovnatelné s C či C++!
TinyGo

Obrázek 5: Grafické porovnání výsledných velikostí spustitelných souborů. 

Autor: tisnik, podle licence: Rights Managed

Pro variantu „Hello world“ volající funkci fmt.Println dostaneme pochopitelně (značně) odlišné hodnoty, z nichž je ovšem stále patrné, že vliv přepínačů překladače je v případě TinyGo značný:

Způsob překladu Velikost
std.Go 2130697
std.TinyGo 1074968
-no-debug 243344
-shecduler=none 238448
-gc=leaking 169872
TinyGo

Obrázek 6: Grafické porovnání výsledných velikostí spustitelných souborů. 

Autor: tisnik, podle licence: Rights Managed

11. Benchmark pro zjištění rychlosti programů přeložených standardním překladačem Go a TinyGo

Prozatím jsme porovnávali velikosti výsledných spustitelných souborů popř. rychlosti překladu. Ovšem taktéž je důležité zjistit, jak rychlé jsou výsledné aplikace, tj. zda se nějak odlišují strojové kódy produkované standardním překladačem jazyka Go v porovnání s výsledky, které získáme v TinyGo (které je postaveno na LLVM). Abychom mohli toto porovnání provést, vrátíme se znovu k benchmarku, který jsme v seriálu o jazyku Go použili. Jedná se o benchmark, ve kterém je počítána Mandelbrotova množina. Příklad je naprogramován takovým způsobem, aby ho později bylo možné upravit do paralelní podoby.

Následuje stručný popis struktury benchmarku.

Zápis (či lépe řečeno export) výsledné bitmapy je realizován v samostatné funkci pojmenované writeImage, které se předají rozměry bitmapy a hodnoty jednotlivých pixelů v poli typu []byte:

func writeImage(width uint, height uint, image []byte) {
}
Poznámka: ve druhé variantě benchmarku nebudeme tuto funkci vůbec volat, protože se ukazuje, že i samotné I/O operace jsou poměrně náročné a současně jsme neimplementovali žádnou formu bufferingu.

Funkce pro výpočet jediného obrazového řádku rastrového obrázku Mandelbrotovy množiny bude mít hlavičku:

func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte, image []byte, cy float64) {
}

Povšimněte si, že v posledním parametru předáváme hodnotu cy, která určuje imaginární složku komplexního čísla C, které vstupuje do iterativního výpočtu Mandelbrotovy množiny.

Volání této funkce a postupné skládání bitmapy tedy může vypadat například takto:

image := make([]byte, width*height*3)
offset := 0
delta := width * 3
 
var cy float64 = -1.5
for y := 0; y < height; y++ {
        calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:], image[offset:offset+delta], cy)
        offset += delta
        cy += 3.0 / float64(height)
}
TinyGo

Obrázek 7: Sekvenční diagram (jediná část článku vytvořená s podporou umělé inteligence ;-/). 

Autor: tisnik, podle licence: Rights Managed

Úplný zdrojový kód dnešního prvního benchmarku tedy bude vypadat následovně:

package main
 
import (
        "bufio"
        "fmt"
        "os"
        "strconv"
)
 
func writeImage(width uint, height uint, image []byte) {
        w := bufio.NewWriter(os.Stdout)
        defer w.Flush()
 
        fmt.Fprintln(w, "P3")
        fmt.Fprintf(w, "%d %d\n", width, height)
        fmt.Fprintln(w, "255")
 
        for i := 0; i < len(image); {
                r := image[i]
                i++
                g := image[i]
                i++
                b := image[i]
                i++
                fmt.Fprintf(w, "%d %d %d\n", r, g, b)
        }
}
 
func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte, image []byte, cy float64) {
        var cx float64 = -2.0
        for x := uint(0); x < width; x++ {
                var zx float64 = 0.0
                var zy float64 = 0.0
                var i uint = 0
                for i < maxiter {
                        zx2 := zx * zx
                        zy2 := zy * zy
                        if zx2+zy2 > 4.0 {
                                break
                        }
                        zy = 2.0*zx*zy + cy
                        zx = zx2 - zy2 + cx
                        i++
                }
                color := palette[i]
                image[3*x] = color[0]
                image[3*x+1] = color[1]
                image[3*x+2] = color[2]
                cx += 3.0 / float64(width)
        }
}
 
func main() {
        if len(os.Args) < 4 {
                println("usage: ./mandelbrot width height maxiter")
                os.Exit(1)
        }
 
        width, err := strconv.Atoi(os.Args[1])
        if err != nil {
                fmt.Printf("Improper width parameter: '%s'\n", os.Args[1])
                os.Exit(1)
        }
 
        height, err := strconv.Atoi(os.Args[2])
        if err != nil {
                fmt.Printf("Improper height parameter: '%s'\n", os.Args[2])
                os.Exit(1)
        }
 
        maxiter, err := strconv.Atoi(os.Args[3])
        if err != nil {
                fmt.Printf("Improper maxiter parameter: '%s'\n", os.Args[3])
                os.Exit(1)
        }
 
        image := make([]byte, width*height*3)
        offset := 0
        delta := width * 3
 
        var cy float64 = -1.5
        for y := 0; y < height; y++ {
                calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:], image[offset:offset+delta], cy)
                offset += delta
                cy += 3.0 / float64(height)
        }
        writeImage(uint(width), uint(height), image)
}

Benchmark je ještě doplněn pomocným souborem s barvovou paletou:

package main
 
/* taken from Fractint */
 
var mandmap = [...][3]byte{
        {255, 255, 255}, {224, 224, 224}, {216, 216, 216}, {208, 208, 208},
        {200, 200, 200}, {192, 192, 192}, {184, 184, 184}, {176, 176, 176},
        {168, 168, 168}, {160, 160, 160}, {152, 152, 152}, {144, 144, 144},
        {136, 136, 136}, {128, 128, 128}, {120, 120, 120}, {112, 112, 112},
        {104, 104, 104}, {96, 96, 96}, {88, 88, 88}, {80, 80, 80},
        {72, 72, 72}, {64, 64, 64}, {56, 56, 56}, {48, 48, 56},
        {40, 40, 56}, {32, 32, 56}, {24, 24, 56}, {16, 16, 56},
        {8, 8, 56}, {000, 000, 60}, {000, 000, 64}, {000, 000, 72},
        {000, 000, 80}, {000, 000, 88}, {000, 000, 96}, {000, 000, 104},
        {000, 000, 108}, {000, 000, 116}, {000, 000, 124}, {000, 000, 132},
        {000, 000, 140}, {000, 000, 148}, {000, 000, 156}, {000, 000, 160},
        {000, 000, 168}, {000, 000, 176}, {000, 000, 184}, {000, 000, 192},
        {000, 000, 200}, {000, 000, 204}, {000, 000, 212}, {000, 000, 220},
        {000, 000, 228}, {000, 000, 236}, {000, 000, 244}, {000, 000, 252},
        {000, 4, 252}, {4, 12, 252}, {8, 20, 252}, {12, 28, 252},
        {16, 36, 252}, {20, 44, 252}, {20, 52, 252}, {24, 60, 252},
        {28, 68, 252}, {32, 76, 252}, {36, 84, 252}, {40, 92, 252},
        {40, 100, 252}, {44, 108, 252}, {48, 116, 252}, {52, 120, 252},
        {56, 128, 252}, {60, 136, 252}, {60, 144, 252}, {64, 152, 252},
        {68, 160, 252}, {72, 168, 252}, {76, 176, 252}, {80, 184, 252},
        {80, 192, 252}, {84, 200, 252}, {88, 208, 252}, {92, 216, 252},
        {96, 224, 252}, {100, 232, 252}, {100, 228, 248}, {96, 224, 244},
        {92, 216, 240}, {88, 212, 236}, {88, 204, 232}, {84, 200, 228},
        {80, 192, 220}, {76, 188, 216}, {76, 180, 212}, {72, 176, 208},
        {68, 168, 204}, {64, 164, 200}, {64, 156, 196}, {60, 152, 188},
        {56, 144, 184}, {52, 140, 180}, {52, 132, 176}, {48, 128, 172},
        {44, 120, 168}, {40, 116, 160}, {40, 108, 156}, {36, 104, 152},
        {32, 96, 148}, {28, 92, 144}, {28, 84, 140}, {24, 80, 136},
        {20, 72, 128}, {16, 68, 124}, {16, 60, 120}, {12, 56, 116},
        {8, 48, 112}, {4, 44, 108}, {000, 36, 100}, {4, 36, 104},
        {12, 40, 108}, {16, 44, 116}, {24, 48, 120}, {28, 52, 128},
        {36, 56, 132}, {40, 60, 140}, {48, 64, 144}, {52, 64, 148},
        {60, 68, 156}, {64, 72, 160}, {72, 76, 168}, {76, 80, 172},
        {84, 84, 180}, {88, 88, 184}, {96, 92, 192}, {104, 100, 192},
        {112, 112, 196}, {124, 120, 200}, {132, 132, 204}, {144, 140, 208},
        {152, 152, 212}, {164, 160, 216}, {172, 172, 220}, {180, 180, 224},
        {192, 192, 228}, {200, 200, 232}, {212, 212, 236}, {220, 220, 240},
        {232, 232, 244}, {240, 240, 248}, {252, 252, 252}, {252, 240, 244},
        {252, 224, 232}, {252, 208, 224}, {252, 192, 212}, {252, 176, 204},
        {252, 160, 192}, {252, 144, 184}, {252, 128, 172}, {252, 112, 164},
        {252, 96, 152}, {252, 80, 144}, {252, 64, 132}, {252, 48, 124},
        {252, 32, 112}, {252, 16, 104}, {252, 000, 92}, {236, 000, 88},
        {228, 000, 88}, {216, 4, 84}, {204, 4, 80}, {192, 8, 76},
        {180, 8, 76}, {168, 12, 72}, {156, 16, 68}, {144, 16, 64},
        {132, 20, 60}, {124, 20, 60}, {112, 24, 56}, {100, 24, 52},
        {88, 28, 48}, {76, 32, 44}, {64, 32, 44}, {52, 36, 40},
        {40, 36, 36}, {28, 40, 32}, {16, 44, 28}, {20, 52, 32},
        {24, 60, 36}, {28, 68, 44}, {32, 76, 48}, {36, 88, 56},
        {40, 96, 60}, {44, 104, 64}, {48, 112, 72}, {52, 120, 76},
        {56, 132, 84}, {48, 136, 84}, {40, 144, 80}, {52, 148, 88},
        {68, 156, 100}, {80, 164, 112}, {96, 168, 124}, {108, 176, 136},
        {124, 184, 144}, {136, 192, 156}, {152, 196, 168}, {164, 204, 180},
        {180, 212, 192}, {192, 220, 200}, {208, 224, 212}, {220, 232, 224},
        {236, 240, 236}, {252, 248, 248}, {252, 252, 252}, {252, 252, 240},
        {252, 252, 228}, {252, 252, 216}, {248, 248, 204}, {248, 248, 192},
        {248, 248, 180}, {248, 248, 164}, {244, 244, 152}, {244, 244, 140},
        {244, 244, 128}, {244, 244, 116}, {240, 240, 104}, {240, 240, 92},
        {240, 240, 76}, {240, 240, 64}, {236, 236, 52}, {236, 236, 40},
        {236, 236, 28}, {236, 236, 16}, {232, 232, 0}, {232, 232, 12},
        {232, 232, 28}, {232, 232, 40}, {236, 236, 56}, {236, 236, 68},
        {236, 236, 84}, {236, 236, 96}, {240, 240, 112}, {240, 240, 124},
        {240, 240, 140}, {244, 244, 152}, {244, 244, 168}, {244, 244, 180},
        {244, 244, 196}, {248, 248, 208}, {248, 248, 224}, {248, 248, 236},
        {252, 252, 252}, {248, 248, 248}, {240, 240, 240}, {232, 232, 232}}

12. Výsledky změřené pro standardní překladač Go

Benchmark bude spuštěn několikrát za sebou. Při každém spuštění se vypočítá Mandelbrotova množina v rozlišení 4096×4096 pixelů s maximálním počtem iterací nastaveným na 255, což je dostatečně velký obrázek a taktéž komplikovaný výpočet na to, aby výpočet trval několik sekund nebo i desítek sekund:

OUTFILE="go.times"
PREFIX="mandelbrot"
RESOLUTION="4096"
MAXITER="255"
 
rm $OUTFILE
 
for i in `seq 10`
do
    echo $i
    /usr/bin/time --output $OUTFILE --append --format "%e %M" ./mandelbrot $RESOLUTION $RESOLUTION $MAXITER > "${PREFIX}_${i}.ppm"
done

Výsledné časy pro variantu benchmarku s exportem obrázků:

6.20 51176
6.18 51304
6.14 51244
6.23 51432
6.24 51048
6.20 51304
6.18 51176
6.19 51176
6.22 51176
6.18 51176

Výsledné časy pro variantu benchmarku bez exportu obrázků:

3.50 51204
3.50 51076
3.49 50820
3.55 50948
3.53 51076
3.50 51204
3.50 51332
3.49 51332
3.49 51204
3.49 51076

13. Optimalizace na rychlost v TinyGo?

Vzhledem k tomu, že TinyGo podporuje optimalizace, které mohou být (ovšem jen pokud jsou povoleny) prováděny překladačem, bude zajímavé zjistit, jak se tyto optimalizace projeví na výsledcích benchmarku a zda bude vůbec benchmark přeložený TinyGo rychlejší, než jeho protějšek, který vznikl překladem pomocí standardního překladače jazyka Go.

Benchmark přeložený TinyGo bez povolení optimalizací (bude se optimalizovat na velikost kódu) bude ve skutečnosti pomalejší, než u standardního Go:

6.78 49408
6.77 49408
6.79 49408
6.76 49408
6.78 49408
6.77 49536
6.84 49408
6.76 49408
6.77 49408
6.77 49536

Pokud povolíme optimalizace přepínačem -opt=2, bude sice benchmark rychlejší, ale pouze nepatrně (což je, popravdě řečeno, poměrně velké zklamání):

6.13 49408
6.12 49536
6.12 49536
6.12 49536
6.13 49408
6.20 49536
6.11 49408
6.07 49408
6.13 49536
6.13 49408

Vypnutí automatického správce paměti nemá na rychlost benchmarku výrazný vliv:

6.25 49188
6.40 49188
6.26 49316
6.25 49060
6.29 49060
6.24 49060
6.63 49060
6.39 49316
6.33 49188
6.24 49060

Ještě se podívejme na časy běhu se zákazem I/O operací (rastrový obrázek se nebude exportovat), čímž omezíme vliv knihovních funkcí. Překlad pomocí TinyGo bez povolení optimalizací:

3.58 648
3.54 648
3.69 580
3.60 520
3.60 520
3.56 520
3.59 700
3.60 520
3.63 648
3.59 648

Výsledek s povolením optimalizací:

3.62 580
3.58 648
3.55 648
3.61 648
3.58 648
3.70 576
3.60 520
3.54 648
3.57 648
3.57 576
Poznámka: zajímavé je, že optimalizace v tomto případě nemají žádný vliv na rychlost prováděných výpočtů.

14. Porovnání změřených výsledků

Z výsledků, které jsme získali měřením, plyne, že TinyGo nemá, i přesto, že jsou povolené optimalizace, výrazně lepší výsledky v porovnání se standardním překladačem jazyka Go. To je zajímavé, protože z časů překladu plyne, že překladač TinyGo zde tráví minimálně řádově více času:

Překlad I/O Volby při překladu Výsledný čas
std.Go povoleno N/A 6.2
std.Go zakázáno N/A 3.5
TinyGo povoleno -Oz 6.8
TinyGo povoleno -O2 6.1
TinyGo povoleno noGC 6.3
TinyGo zakázáno -Oz 3.6
TinyGo zakázáno -O2 3.6
TinyGo

Obrázek 8: Porovnání výsledků benchmarků. 

Autor: tisnik, podle licence: Rights Managed

15. Druhá varianta benchmarku: výpočet jednotlivých řádků v samostatných gorutinách

Mnoho aplikací naprogramovaných v jazyku Go využívá koncept kanálů a gorutin, tj. rozdělení celého výpočtu do menších celků, které mohou být spuštěny souběžně a v mnoha případech i paralelně. Bude tedy užitečné si ověřit, jak bude řízen výpočet s mnoha stovkami nebo jednotkami tisíc gorutin, a to jak runtime systémem původního Go, tak i v TinyGo (kde lze navíc runtime do jisté míry ovlivnit). Původní benchmark změníme tak, aby využíval velké množství gorutin. Celý výpočet prováděný benchmarkem proto upravíme do takové podoby, aby byly výpočty prováděny souběžně (a do určité míry i paralelně). Barvy pixelů na každém obrazovém řádku budou vypočteny v samostatné gorutině. Vzhledem k tomu, že gorutiny jsou interně reprezentovány úsporným způsobem, je možné tuto optimalizaci bez problémů provést a vytvořit jich tak i několik tisíc (v závislosti na rozlišení bitmapy, resp. přesněji řečeno na počtu obrazových řádků bitmapy).

O to, aby se počkalo na dokončení všech gorutin, se postará kanál pojmenovaný done, jehož kapacita přesně odpovídá počtu gorutin. Nejprve se kanál vytvoří, následně se všechny gorutiny spustí a na konci čtením z kanálu počkáme na dokončení všech height gorutin:

done := make(chan bool, height)
 
// na tomto místě bude umístěn vlastní výpočet
 
for i := 0; i < height; i++ {
        <-done
}
Poznámka: ovšem synchronizaci lze provádět i odlišnými prostředky, například atomickým čítačem atd.

Jedinou další úpravou bude modifikace funkce calcMandelbrot, které se musí předat reference na kanál, do kterého se na konci výpočtu zapíše hodnota true (důležitý je zápis, nikoli vlastní hodnota, ideální by byl kanál pro prázdné struktury):

func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte, image []byte, cy float64, done chan bool) {
        ...
        ...
        ...
        done <- true
}

V hlavní gorutině počkáme na dokončení všech výpočtů takto:

        for i := 0; i < height; i++ {
                <-done
        }

Následuje (volitelný) zápis výsledků do externího rastrového obrázku:

        writeImage(uint(width), uint(height), image)

Celý zdrojový kód upraveného benchmarku vypadá následovně:

package main
 
import (
        "bufio"
        "fmt"
        "os"
        "strconv"
)
 
func writeImage(width uint, height uint, image []byte) {
        w := bufio.NewWriter(os.Stdout)
        defer w.Flush()
 
        fmt.Fprintln(w, "P3")
        fmt.Fprintf(w, "%d %d\n", width, height)
        fmt.Fprintln(w, "255")
 
        for i := 0; i < len(image); {
                r := image[i]
                i++
                g := image[i]
                i++
                b := image[i]
                i++
                fmt.Fprintf(w, "%d %d %d\n", r, g, b)
        }
}
 
func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte, image []byte, cy float64, done chan bool) {
        var cx float64 = -2.0
        for x := uint(0); x < width; x++ {
                var zx float64 = 0.0
                var zy float64 = 0.0
                var i uint = 0
                for i < maxiter {
                        zx2 := zx * zx
                        zy2 := zy * zy
                        if zx2+zy2 > 4.0 {
                                break
                        }
                        zy = 2.0*zx*zy + cy
                        zx = zx2 - zy2 + cx
                        i++
                }
                color := palette[i]
                image[3*x] = color[0]
                image[3*x+1] = color[1]
                image[3*x+2] = color[2]
                cx += 3.0 / float64(width)
        }
        done <- true
}
 
func main() {
        if len(os.Args) < 4 {
                println("usage: ./mandelbrot width height maxiter")
                os.Exit(1)
        }
 
        width, err := strconv.Atoi(os.Args[1])
        if err != nil {
                fmt.Printf("Improper width parameter: '%s'\n", os.Args[1])
                os.Exit(1)
        }
 
        height, err := strconv.Atoi(os.Args[2])
        if err != nil {
                fmt.Printf("Improper height parameter: '%s'\n", os.Args[2])
                os.Exit(1)
        }
 
        maxiter, err := strconv.Atoi(os.Args[3])
        if err != nil {
                fmt.Printf("Improper maxiter parameter: '%s'\n", os.Args[3])
                os.Exit(1)
        }
 
        done := make(chan bool, height)
 
        image := make([]byte, width*height*3)
        offset := 0
        delta := width * 3
 
        var cy float64 = -1.5
        for y := 0; y < height; y++ {
                go calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:], image[offset:offset+delta], cy, done)
                offset += delta
                cy += 3.0 / float64(height)
        }
        for i := 0; i < height; i++ {
                <-done
        }
        writeImage(uint(width), uint(height), image)
}

16. Porovnání změřených výsledků benchmarků

Opět se podívejme na výsledky získané benchmarkem pro různé překladače a runtime jazyka Go i pro různé parametry překladu (volitelné pouze v případě TinyGo):

Překlad I/O Volby při překladu Výsledný čas
std.Go povoleno N/A 3.5
std.Go zakázáno N/A 0.5
TinyGo povoleno -Oz 4.3
TinyGo povoleno -O2 3.4
TinyGo povoleno noGC 3.6
TinyGo zakázáno -Oz 0.74
TinyGo zakázáno -O2 0.73

Z výsledků opět plyne, že benchmark přeložený s využitím TinyGo není rychlejší. Naopak, zdá se, že práce s gorutinami je v TinyGo náročnější, než v případě standardního jazyka Go, protože při zákazu I/O operací trval celý benchmark v případě std. Go pouze 0,5 sekundy, zatímco v TinyGo 0,73 sekundy. Vše si samozřejmě můžeme zobrazit i v grafické podobě:

TinyGo

Obrázek 9: Porovnání výsledků benchmarků. 

Autor: tisnik, podle licence: Rights Managed

17. Překlad do WebAssembly

TinyGo se stává populární pro vývoj těch aplikací, které mají běžet ve webovém prohlížeči, konkrétně ve virtuálním stroji WebAssembly. Zde může být kritická zejména celková velikost souborů .wasm a TinyGo je tedy výhodné, protože dokáže tyto soubory generovat mnohem menší, než je tomu u standardního Go (a jeho rozsáhlého ekosystému). Ostatně si to můžeme ověřit, například překladem demonstračního příkladu „Hello world“ do formátu WebAssembly.

Překlad do WebAssembly pomocí standardního Go:

$ GOOS=js GOARCH=wasm go build -o hello.wasm

Výsledný soubor bude mít velikost přesahující 1,5 MB:

$ file hello.wasm
hello.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
 
$ ls -l hello.wasm
-rwxr-xr-x. 1 ptisnovs ptisnovs 1600338 Dec  8 11:40 hello.wasm

Překlad stejného příkladu, tentokrát s využitím překladače TinyGo:

$ GOOS=js GOARCH=wasm tinygo build -o hello.wasm hello.go

Výsledný soubor je v tomto případě mnohem menší – cca 96 kB:

$ file hello.wasm
hello.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
 
$ ls -l hello.wasm
-rwxr-xr-x. 1 ptisnovs ptisnovs 97036 Dec  8 11:43 hello.wasm

Podobné rozdíly nalezneme i při překladu benchmarku do WebAssembly. Druhý benchmark je v případě použití TinyGo přeložen do binárního souboru ve WebAssembly, který má velikost 297 kB, zatímco v případě standardního Go vznikne soubor o velikosti 2,5 MB!

18. Závěr

TinyGo je zajímavou a taktéž užitečnou alternativou ke standardnímu překladači (a runtime systému) programovacího jazyka Go. Mezi jeho největší přednosti patří obecně menší velikost výsledných spustitelných souborů, což ocení zejména programátoři vytvářející aplikace pro WebAssembly nebo pro mikrořadiče (což je téma, kterému jsme se prozatím nevěnovali, ale které si vyžádá samostatný článek). Teoreticky by taktéž měl překladač TinyGo generovat výkonnější kód, což jsme ovšem v benchmarcích neověřili – v některých případech naopak vyhrává rychlý standardní překladač Go. Velkou nevýhodou TinyGo je pomalá rychlost překladu, což je zvláště bolestné právě v ekosystému programovacího jazyka Go, který je postaven na předpokladu, že se celá aplikace vždy přeloží jako celek, bez rozdělení na fázi odděleného překladu následované fází slinkování.

TinyGo tedy není vhodné chápat jako náhradu původní sady nástrojů jazyka Go, ale spíše o její doplnění.

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně pět až šest megabajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Příklad Stručný popis Cesta
1 hello.go program typu „Hello world“, který volá přímo funkci println https://github.com/tisnik/go-root/blob/master/tinygo/hello.go
2 hello2.go program typu „Hello world“, který volá knihovní funkci fmt.Println https://github.com/tisnik/go-root/blob/master/tinygo/hello2.go
       
3 mandelbrot-concurrent/mandelbrot.go benchmark pro výpočet Mandelbrotovy množiny, sekvenční varianta https://github.com/tisnik/go-root/blob/master/tinygo/mandelbrot-concurrent/mandelbrot.go
4 mandelbrot-concurrent/mandelbrot_no_out.go varianta benchmarku bez exportu výsledku https://github.com/tisnik/go-root/blob/master/tinygo/mandelbrot-concurrent/mandelbrot_no_out.go
5 mandelbrot-concurrent/palettes.go barvová paleta použitá benchmarkem https://github.com/tisnik/go-root/blob/master/tinygo/mandelbrot-concurrent/palettes.go
       
6 mandelbrot-linewise/mandelbrot.go benchmark pro výpočet Mandelbrotovy množiny, souběžná/paralelní varianta https://github.com/tisnik/go-root/blob/master/tinygo/mandelbrot-linewise/mandelbrot.go
7 mandelbrot-linewise/mandelbrot_no_out.go varianta benchmarku bez exportu výsledku https://github.com/tisnik/go-root/blob/master/tinygo/mandelbrot-linewise/mandelbrot_no_out.go
8 mandelbrot-linewise/palettes.go barvová paleta použitá benchmarkem https://github.com/tisnik/go-root/blob/master/tinygo/mandelbrot-linewise/palettes.go

20. Odkazy na Internetu

  1. TinyGo – A Go Compiler For Small Places
    https://tinygo.org/
  2. Getting started
    https://tinygo.org/getting-started/
  3. Go.dev (klasická varianta překladače jazyka Go)
    https://go.dev/
  4. gccgo
    https://gcc.gnu.org/onlinedocs/gccgo/
  5. Setting up and using gccgo
    https://go.dev/doc/install/gccgo
  6. Awesome Go
    https://github.com/avelino/awesome-go
  7. TinyGo: Inline assembly
    https://tinygo.org/docs/con­cepts/compiler-internals/inline-assembly/
  8. Getting Started with TinyGo: Bringing Go to Microcontrollers and WebAssembly
    https://dev.to/ekwoster/getting-started-with-tinygo-bringing-go-to-microcontrollers-and-webassembly-2pp0
  9. Optimizing Go code with GCCGO for improved performance
    https://dev.to/parmcoder/optimizing-go-code-with-gccgo-for-improved-performance-2d3d
  10. The Untold Power of TinyGo: How to Run Go on Microcontrollers and Supercharge Embedded Development
    https://dev.to/ekwoster/the-untold-power-of-tinygo-how-to-run-go-on-microcontrollers-and-supercharge-embedded-development-2g7d
  11. From Arduino to Mars: Why You Should Be Using TinyGo for Embedded Web Development
    https://dev.to/ekwoster/from-arduino-to-mars-why-you-should-be-using-tinygo-for-embedded-web-development-54od
  12. Optimizing binaries
    https://tinygo.org/docs/gu­ides/optimizing-binaries/
  13. Why TinyGo Might Be the Future of Embedded WebAssembly & How To Get Started Today
    https://ekwoster.dev/post/-why-tinygo-might-be-the-future-of-embedded-webassembly-how-to-get-started-today/
  14. TinyGo na GitHubu
    https://github.com/tinygo-org/tinygo
  15. Compile Go directly to WebAssembly components with TinyGo and WASI P2
    https://wasmcloud.com/blog/compile-go-directly-to-webassembly-components-with-tinygo-and-wasi-p2/
  16. Do you use gccgo?
    https://www.reddit.com/r/go­lang/comments/j1g1z6/do_y­ou_use_gccgo/
  17. Go v/s TinyGo: Which one is the best for you?
    https://blog.nonstopio.com/go-v-s-tinygo-which-one-is-the-best-for-you-73cac3c7849e
  18. Go Wiki: GccgoCrossCompilation
    https://go.dev/wiki/Gccgo­CrossCompilation
  19. Oficiální stránky Gccgo
    https://gcc.gnu.org/online­docs/gccgo/index.html
  20. What are the primary differences between ‚gc‘ and ‚gccgo‘?
    https://stackoverflow.com/qu­estions/25811445/what-are-the-primary-differences-between-gc-and-gccgo
  21. Setting up and using gccgo
    https://go.dev/doc/install/gccgo
  22. Go (Arch Linux)
    https://wiki.archlinux.or­g/title/Talk:Go
  23. Why are binaries built with gccgo smaller (among other differences?)
    https://stackoverflow.com/qu­estions/27067112/why-are-binaries-built-with-gccgo-smaller-among-other-differences
  24. Why Everyone Is Sleeping On TinyGo: Run Go on Microcontrollers and the Web (WASM) Today!
    https://ekwoster.dev/post/-why-everyone-is-sleeping-on-tinygo-run-go-on-microcontrollers-and-the-web-wasm-today/
  25. Go (Golang) GOOS and GOARCH
    https://gist.github.com/a­sukakenji/f15ba7e588ac42795f421­b48b8aede63

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.