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ů
19. Repositář s demonstračními příklady
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.
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.
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/asukakenji/f15ba7e588ac42795f421b48b8aede63, 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.
$ 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.
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.
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/workflows/linux.yml?query=branch%3Adev. 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
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.
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
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 |
Č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:
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 |
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 |
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) {
}
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)
}
Ú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
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 |
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
}
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ě:
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
- TinyGo – A Go Compiler For Small Places
https://tinygo.org/ - Getting started
https://tinygo.org/getting-started/ - Go.dev (klasická varianta překladače jazyka Go)
https://go.dev/ - gccgo
https://gcc.gnu.org/onlinedocs/gccgo/ - Setting up and using gccgo
https://go.dev/doc/install/gccgo - Awesome Go
https://github.com/avelino/awesome-go - TinyGo: Inline assembly
https://tinygo.org/docs/concepts/compiler-internals/inline-assembly/ - 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 - Optimizing Go code with GCCGO for improved performance
https://dev.to/parmcoder/optimizing-go-code-with-gccgo-for-improved-performance-2d3d - 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 - 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 - Optimizing binaries
https://tinygo.org/docs/guides/optimizing-binaries/ - 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/ - TinyGo na GitHubu
https://github.com/tinygo-org/tinygo - 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/ - Do you use gccgo?
https://www.reddit.com/r/golang/comments/j1g1z6/do_you_use_gccgo/ - 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 - Go Wiki: GccgoCrossCompilation
https://go.dev/wiki/GccgoCrossCompilation - Oficiální stránky Gccgo
https://gcc.gnu.org/onlinedocs/gccgo/index.html - What are the primary differences between ‚gc‘ and ‚gccgo‘?
https://stackoverflow.com/questions/25811445/what-are-the-primary-differences-between-gc-and-gccgo - Setting up and using gccgo
https://go.dev/doc/install/gccgo - Go (Arch Linux)
https://wiki.archlinux.org/title/Talk:Go - Why are binaries built with gccgo smaller (among other differences?)
https://stackoverflow.com/questions/27067112/why-are-binaries-built-with-gccgo-smaller-among-other-differences - 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/ - Go (Golang) GOOS and GOARCH
https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63









