Hlavní navigace

Trasování a profilování aplikací naprogramovaných v Go

Pavel Tišnovský

Dnes si ukážeme základní způsoby trasování a profilování aplikací, pochopitelně i s vyhodnocením naměřených výsledků. Použijeme přitom i ty nástroje, které jsou součástí standardní instalace programovacího jazyka Go.

Doba čtení: 40 minut

Sdílet

11. Spuštění paměťového profileru a základní analýza výsledků

12. Úprava demonstračního příkladu: paměťově náročnější operace

13. Analýza výsledků získaných profilery

14. Podrobnější výsledky získané CPU profilerem

15. Demonstrační příklad využívající gorutiny

16. Výsledky měření CPU profileru

17. Spuštění profileru přímo z vyvíjené aplikace

18. Podrobnější výsledky CPU profileru

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

20. Odkazy na Internetu

1. Trasování a profilování aplikací naprogramovaných v Go

Při tvorbě a především při ladění a optimalizacích aplikací se kromě debuggeru (a pochopitelně i logování) mnohdy používají i další pomocné nástroje. Typicky se jedná o profiler a někdy též o nástroj umožňující trasování kódu. Tyto nástroje (především profiler) jsou pochopitelně dostupné i vývojářům používajícím programovací jazyk Go; dokonce oba zmíněné nástroje nalezneme v sadě základních nástrojů dodávaných přímo s tímto programovacím jazykem.

Pokud se na celý problém podíváme obecněji, používají vývojáři minimálně šest typů diagnostických nástrojů:

  1. Některý z dostupných debuggerů, které umožňují mj. i pozastavení běhu programu a zjištění jeho aktuálního stavu (globální a lokální proměnné, gorutiny, zásobníkové rámce, mutexy atd.), krokování programu, definování breakpointů a watchpointů atd. V případě ekosystému programovacího jazyka Go se používají především dva debuggery. Prvním z nich je GNU Debugger, druhým pak debugger nazvaný Delve.
  2. Dalším typem diagnostického nástroje je již výše zmíněný profiler, který nám umožňuje zjistit frekvenci volaných funkcí, kolik strojového a reálného času se stráví ve volaných funkcích (včetně kumulativního času), informace o spotřebě paměti apod. V případě programovacího jazyka Go se používá profiler, který je přímo součástí základních nástrojů poskytovaných k tomuto jazyku.
  3. Třetím typem diagnostického nástroje je tracer, jenž slouží pro přesnější analýzu toku programu, měření propustnosti i zpoždění v jednotlivých částech apod. V případě programovacího jazyka Go je opět možné použít nástroj, který je standardní součástí instalace.
  4. Dalšími typy diagnostických nástrojů jsou nástroje sledující různé události, které vznikají při běhu aplikace. V programovacím jazyku Go se systémem automatické správy paměti se jedná především o události související s alokací paměti, spuštěním garbage collectoru, události souvisejícími s gorutinami atd.
  5. Mnohdy je taktéž velmi užitečné sledovat, jaké systémové funkce sledovaná aplikace volá. Pro tento účel se používá standardní nástroj nazvanýstrace, který je pochopitelně použitelný i pro aplikace naprogramované v jazyku Go. S možnostmi nabízenými tímto nástrojem se seznámíme v dalších kapitolách.
  6. A konečně nesmíme zapomenout na nástroj ltrace určený pro zjištění volání funkcí z dynamicky linkovaných knihoven (.so, .dll). Na jednu stranu je možné říci, že pro jazyk Go má tento nástroj spíše menší význam, protože Go aplikace je většinou slinkovaná staticky, ovšem v případě, že je skutečně zapotřebí z jazyka Go volat nativní funkce z dynamicky linkované knihovny (například SDL2), může být nástroj ltrace velmi užitečný pro hledání případných problémů, které nemusí být vždy odhaleny samotným překladačem Go.

2. Standardní nástroje, které nejsou specifické pouze pro jazyk Go

Na začátek si připomeňme, že v současnosti existují (dnes de facto již standardní) nástroje využívané pro ladění jádra popř. pro ladění a/nebo sledování použití základních knihoven, dynamicky linkovaných knihoven atd. Jak může vypadat použití jednotlivých nástrojů na celém aplikačním „stacku“, tj. od trasování/ladění samotné uživatelské aplikace až přes řešení problémů přímo v jádře operačního systému, je naznačeno na následujícím schématu, s nímž jsme se již na Rootu seznámili v souvislosti s popisem debuggerů:

+----------+
|          |..... gdb
| aplikace |
|          |..... SystemTap
+----------+
     |
     |
     |...... ltrace
     |
     v
+----------+
|          |..... gdb
|  glibc   |
|          |..... SystemTap
+----------+
     |
     |
     |...... strace
     |
     v
+----------+
|          |..... SystemTap
|  jádro   |
|          |..... KGDB
+----------+

3. Utilita ltrace – výpis informací o volaných knihovních funkcích

Prvním nástrojem, s nímž se v dnešním článku ve stručnosti seznámíme, je již výše zmíněná utilitka nazvaná jednoduše ltrace. Tento nástroj je možné použít ve chvíli, kdy potřebujeme zjistit, které knihovní funkce z dynamicky linkovaných knihoven (.so, .dll) se volají popř. jak často se tyto funkce volají a kolik času v nich některá aplikace tráví. Takto zjištěné statistické informace lze později použít různým způsobem, například pro vyhledání problémových či pomalých částí kódu, zjištění, ve kterých místech dochází k chybě, zjištění způsobů alokace a dealokace paměti (funkce malloc, free a příbuzné), popř. k jednoduchému trasování (což je asi nejběžnější).

Tato utilitka může mít v případě programovacího jazyka Go relativně malé možnosti využití, a to z toho důvodu, že aplikace psané v Go jsou typicky staticky linkované a mnohdy žádné další funkce z dynamicky linkovaných knihoven nepoužívají, což si ostatně můžete snadno ověřit na nějaké přeložené aplikaci dalším nástrojem ldd:

$ go build chessboard.go
 
$ ldd chessboard
 
        not a dynamic executable
Poznámka: pokud ovšem použijete například balíček net/http, bude se linkovat minimálně standardní céčková knihovna (opět si to můžete velmi snadno ověřit).

Ovšem pro otestování, jak nástroj ltrace pracuje, si ukážeme jednoduchý program vyvinutý v jazyce Go, který volá céčkovou knihovní funkci nazvanou random a taktéž funkci srand (stdlib.h). Tento program používá poměrně obskurní způsob zajišťující základní interoperabilitu mezi jazykem Go a nativními (typicky céčkovými) knihovnami:

package main
 
// #include <stdlib.h>
import "C"
 
func main() {
        C.srand(C.uint(42))
 
        for i := 1; i <= 10; i++ {
                x := C.random()
                println(x)
        }
}
Poznámka: povšimněte si komentáře zapsaného nad příkazem import „C“. Tento komentář je rozeznáván nástrojem cgo [1] a použije se při transpilaci našeho zdrojového kódu do jazyka C (celý překlad přes cgo je relativně komplikovaný a jeho podrobnější popis vydá na samostatný článek). Dále stojí za zmínku i vlastní import import „C“, protože žádný balíček nazvaný „C“ ve skutečnosti neexistuje. Jedná se totiž o takzvaný pseudobalíček, který je opět zpracováván překladačem cgo. Všechny céčkovské funkce z nativní knihovny se v tomto případě volají s uvedením jména pseudobalíčku (zde se již volání nijak neliší od funkcí a metod z běžných balíčků jazyka Go).

Samotný překlad aplikace zajistí (alespoň v tomto velmi jednoduchém případě) známý příkaz:

$ go build clibs.go

Interně se ovšem v tomto případě bude volat výše zmíněný nástroj cgo.

Poznámka: bližší informace o překladu aplikací získáte příkazem:
$ go help build

4. Příklad použití utility ltrace i pro aplikace psané v Go

Nejprve si ověřme, že překladem výše vypsaného programu clibs.go skutečně vznikl spustitelný binární soubor, který používá dynamicky linkované knihovny. Opět se spolehneme na nástroj ldd:

$ ldd --verbose clibs
 
        linux-vdso.so.1 =>  (0x00007ffc7bf0d000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f6b31bbf000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6b317f6000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f6b31ddd000)
 
        Version information:
        ./clibs:
                libpthread.so.0 (GLIBC_2.3.2) => /lib/x86_64-linux-gnu/libpthread.so.0
                libpthread.so.0 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libpthread.so.0
                libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
        /lib/x86_64-linux-gnu/libpthread.so.0:
                ld-linux-x86-64.so.2 (GLIBC_2.2.5) => /lib64/ld-linux-x86-64.so.2
                ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2
                ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2
                libc.so.6 (GLIBC_2.14) => /lib/x86_64-linux-gnu/libc.so.6
                libc.so.6 (GLIBC_2.3.2) => /lib/x86_64-linux-gnu/libc.so.6
                libc.so.6 (GLIBC_PRIVATE) => /lib/x86_64-linux-gnu/libc.so.6
                libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
        /lib/x86_64-linux-gnu/libc.so.6:
                ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2
                ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2

Vidíme, že se skutečně používají některé nativní knihovny, především pak knihovna libc.so (ta je na výpisu zvýrazněna).

Nyní tedy můžeme použít příkaz ltrace a spustit přes něj binární spustitelnou aplikace clibs:

$ ltrace ./clibs

První řádky výpisu jsou pro nás prozatím nezajímavé:

__libc_start_main(0x447ac0, 1, 0x7ffd7174df58, 0x4511b0 <unfinished ...>
malloc(56)                                                       = 0xd88010
pthread_attr_init(0xd88010, 0xd88040, 0xd88010, 0x7f8fb562b760)  = 0
pthread_attr_getstacksize(0xd88010, 0x7ffd7174de28, 0xd88010, 0) = 0
pthread_attr_destroy(0xd88010, 1, 0x800000, 0)                   = 0
free(0xd88010)                                                   = <void>
mmap(0, 0x40000, 3, 34)                                          = 0x7f8fb5a0d000
mmap(0xc000000000, 0x4000000, 0, 34)                             = 0xc000000000
mmap(0xc000000000, 0x4000000, 3, 50)                             = 0xc000000000
mmap(0, 0x2000000, 3, 34)                                        = 0x7f8fb3269000
mmap(0, 0x210000, 3, 34)                                         = 0x7f8fb3059000
mmap(0, 0x10000, 3, 34)                                          = 0x7f8fb5a62000
mmap(0, 0x10000, 3, 34)                                          = 0x7f8fb5a52000
sigaction(SIGHUP, nil, { 0, <>, 0x1, 0 })                        = 0
sigismember(<>, SIGHUP)                                          = 0
...
...
...

Nejzajímavější informace nalezneme až na konci výpisu. Jsou zde uvedeny informace o volání funkce srand() a dále informace o tom, že se desetkrát zavolala funkce random(), společně s výslednou (návratovou) hodnotou této funkce. Tyto informace jsou proloženy zprávami vypisovanými přímo z Go kódu funkcí println(), takže si můžete snadno ověřit, že nám ltrace skutečně poskytuje relevantní informace:

srand(42, 0x6c08a0, 0xc0000386f8, 0xc000038770)                  = 0
random()                                                         = 71876166
71876166
random()                                                         = 708592740
708592740
random()                                                         = 1483128881
1483128881
random()                                                         = 907283241
907283241
random()                                                         = 442951012
442951012
random()                                                         = 537146758
537146758
random()                                                         = 1366999021
1366999021
random()                                                         = 1854614940
1854614940
random()                                                         = 647800535
647800535
random()                                                         = 53523743
53523743
+++ exited (status 0) +++
Poznámka: nenechte se zmást tím, že ltrace zobrazuje čtyři parametry funkce srand(). Nástroj ltrace totiž musí znát počet parametrů funkcí (potom je zobrazuje korektně i se správným typem); pokud tuto informaci z nějakého důvodu nemá k dispozici, zobrazí buď část zásobníkového rámce, nebo obsah registrů používaných pro přenos parametrů do volaných funkcí (podle použité ABI, na x86–64 se konkrétně jedná o registry).

5. Další užitečné možnosti nabízené nástrojem ltrace

V případě, že budeme potřebovat zjistit čas volání nějaké funkce, je možné použít přepínač -t. Výsledek pak může vypadat takto:

$ ltrace -t ./clibs
 
21:51:25 __libc_start_main(0x447ac0, 1, 0x7ffd0cdf0f48, 0x4511b0
21:51:25 malloc(56)                                              = 0x1c40010
21:51:25 pthread_attr_init(0x1c40010, 0x1c40040, 0x1c40010, 0x7ffa14c34760) = 0
21:51:25 pthread_attr_getstacksize(0x1c40010, 0x7ffd0cdf0e18, 0x1c40010, 0) = 0
21:51:25 pthread_attr_destroy(0x1c40010, 1, 0x800000, 0)         = 0
21:51:25 free(0x1c40010)                                         =
...
...
...
21:51:28 srand(42, 0x6c08a0, 0xc0000386f8, 0xc000038770)         = 0
21:51:28 random()                                                = 71876166
21:51:28 random()                                                = 708592740
21:51:28 random()                                                = 1483128881
21:51:28 random()                                                = 907283241
21:51:28 random()                                                = 442951012
21:51:28 random()                                                = 537146758
21:51:28 random()                                                = 1366999021
21:51:28 random()                                                = 1854614940
21:51:28 random()                                                = 647800535
21:51:28 random()                                                = 53523743
21:51:28 +++ exited (status 0) +++

Ve chvíli, kdy se namísto přepínače -t použije přepínač -tt (dvojité té), zvětší se přesnost změřeného a vypsaného času na milisekundy:

$ ltrace -tt ./clibs
 
21:52:49.436090 __libc_start_main(0x447ac0, 1, 0x7ffd884b8778, 0x4511b0
21:52:49.436595 malloc(56)                                       = 0xd1b010
...
...
...
21:52:52.536276 srand(42, 0x6c08a0, 0xc0000386f8, 0xc000038770)  = 0
21:52:52.537378 random()                                         = 71876166
21:52:52.539556 random()                                         = 708592740
21:52:52.541733 random()                                         = 1483128881
21:52:52.543911 random()                                         = 907283241
21:52:52.546049 random()                                         = 442951012
21:52:52.547190 random()                                         = 537146758
21:52:52.548342 random()                                         = 1366999021
21:52:52.549460 random()                                         = 1854614940
21:52:52.550605 random()                                         = 647800535
21:52:52.551741 random()                                         = 53523743
21:52:52.553253 +++ exited (status 0) +++

Samozřejmě je možné zjistit nejenom okamžik, kdy se do funkce vstupuje, ale i dobu trvání knihovních funkcí, a to díky přepínači -r:

$ ltrace -r ./clibs
 
  0.000000 __libc_start_main(0x447ac0, 1, 0x7fff4a807468, 0x4511b0
  ...
  ...
  ...
  0.001263 srand(42, 0x6c08a0, 0xc0000386f8, 0xc000038770)       = 0
  0.001114 random()                                              = 71876166
  0.001211 random()                                              = 708592740
  0.001210 random()                                              = 1483128881
  0.001204 random()                                              = 907283241
  0.001181 random()                                              = 442951012
  0.001154 random()                                              = 537146758
  0.001228 random()                                              = 1366999021
  0.001198 random()                                              = 1854614940
  0.001218 random()                                              = 647800535
  0.001203 random()                                              = 53523743
  0.001549 +++ exited (status 0) +++

Mnohdy nepotřebujeme zjistit všechny volané knihovní funkce, ale pouze vypranou podmnožinu z nich. Zde přichází ke slovu přepínač -e, kterému můžeme předat seznam funkcí, které nás zajímají. Funkce se zapisují ve formě jednoduchého jazyka, což například znamená, že pro filtraci těch funkcí, jejichž volání nás zajímá, se vkládají znaky +:

$ ltrace -e srand+random ./clibs
 
clibs-→srand(42, 0x6c08a0, 0xc0000386f8, 0xc000038770)           = 0
clibs-→random()                                                  = 71876166
clibs-→random()                                                  = 708592740
clibs-→random()                                                  = 1483128881
clibs-→random()                                                  = 907283241
clibs-→random()                                                  = 442951012
clibs-→random()                                                  = 537146758
clibs-→random()                                                  = 1366999021
clibs-→random()                                                  = 1854614940
clibs-→random()                                                  = 647800535
clibs-→random()                                                  = 53523743
+++ exited (status 0) +++

Velmi užitečná je volba -c, po jejímž zadání nástroj ltrace zobrazí statistiku volání jednotlivých funkcí s celkovým i s průměrným časem, který program v dané funkci strávil. Opět si uveďme příklady:

$ ltrace -c ./clibs 
 
% time     seconds  usecs/call     calls      function
------ ----------- ----------- --------- --------------------
 47.35    0.682426         190      3584 sigismember
 47.29    0.681610         190      3584 sigaddset
  1.90    0.027409         232       118 sigaction
  0.76    0.010957         188        58 sigemptyset
  0.63    0.009018         901        10 random
  0.50    0.007277        1819         4 pthread_create
  0.32    0.004574         571         8 pthread_sigmask
  0.16    0.002357         589         4 pthread_detach
  0.16    0.002312         462         5 pthread_attr_init
  0.16    0.002285         457         5 pthread_attr_getstacksize
  0.15    0.002138         427         5 malloc
  0.15    0.002098         524         4 sigfillset
  0.13    0.001817         259         7 mmap
  0.07    0.001060         176         6 __errno_location
  0.06    0.000880         880         1 srand
  0.06    0.000872         872         1 pthread_cond_broadcast
  0.06    0.000870         870         1 pthread_mutex_unlock
  0.06    0.000866         866         1 pthread_mutex_lock
  0.02    0.000240         240         1 free
  0.01    0.000206         206         1 pthread_attr_destroy
------ ----------- ----------- --------- --------------------
100.00    1.441272                  7408 total
Poznámka: v tabulce jsem zvýraznil obě přímo volané funkce. Z výpisu je patrné, že ani jedna z těchto funkcí nepředstavuje úzké místo programu.

6. Utilita strace – výpis informací o volaných systémových funkcích

Druhým nástrojem, s nímž se dnes alespoň ve stručnosti seznámíme, je utilita nazvaná příhodně strace. Zatímco výše popsaná ltrace sloužila k výpisu volaných knihovních funkcí (pocházejících například z knihovny glibc), je utilita strace určena ke zjištění systémových volání (syscalls), a to nezávisle na tom, kde toto volání vzniklo (většinou se jedná opět o knihovnu glibc, ovšem nemusí tomu tak být vždycky. Podobně jako v případě ltrace je možné nástroj strace použít buď pro spuštění laděné/trasované aplikace, nebo je možné se přes přepínač -p{pid}) připojit k již běžící aplikaci (to je velmi užitečné například při sledování běhu „živých“ dlouhoběžících serverových aplikací apod.). Význam některých přepínačů je u straceltrace shodný, což je samozřejmě výhodné.

$ strace -help
 
usage: strace [-CdffhiqrtttTvVxxy] [-I n] [-e expr]...
              [-a column] [-o file] [-s strsize] [-P path]...
              -p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]
   or: strace -c[df] [-I n] [-e expr]... [-O overhead] [-S sortby]
              -p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]
              ...
              ...
              ...

7. Příklad použití utility strace pro aplikaci kopírující souboru

Podívejme se nyní na základní způsob použití nástroje strace. Pro jednoduchost tento nástroj vyzkoušíme na aplikaci pro kopii souboru:

package main
 
import (
        "fmt"
        "io"
        "os"
)
 
func closeFile(file *os.File) {
        fmt.Printf("Closing file '%s'\n", file.Name())
        file.Close()
}
 
func copyFile(srcName, dstName string) (written int64, err error) {
        src, err := os.Open(srcName)
        if err != nil {
                panic(err)
        }
        defer closeFile(src)
 
        dst, err := os.Create(dstName)
        if err != nil {
                panic(err)
        }
        defer closeFile(dst)
 
        buffer := make([]byte, 16)
        copied := int64(0)
 
        for {
                read, err := src.Read(buffer)
 
                if read > 0 {
                        fmt.Printf("read %d bytes\n", read)
                        written, err := dst.Write(buffer[:read])
                        if written > 0 {
                                fmt.Printf("written %d bytes\n", written)
                        }
                        if err != nil {
                                fmt.Printf("write error %v\n", err)
                                return copied, err
                        }
                        copied += int64(written)
                }
 
                if err == io.EOF {
                        fmt.Println("reached end of file")
                        break
                }
 
                if err != nil {
                        fmt.Printf("other error %v\n", err)
                        return copied, err
                }
        }
        return copied, nil
}
 
func testCopyFile(srcName, dstName string) {
        copied, err := copyFile(srcName, dstName)
        if err != nil {
                fmt.Printf("copyFile('%s', '%s') failed!!!\n", srcName, dstName)
        } else {
                fmt.Printf("Copied %d bytes\n", copied)
        }
        fmt.Println()
}
 
func main() {
        testCopyFile("test_input.txt", "output.txt")
}

Logika programu je pravděpodobně zřejmá – otevře první soubor pro čtení, druhý pro zápis a posléze provádí kopii dat po blocích o velikosti šestnácti bajtů. Ovšem z pohledu systémových volání je tento program mnohem složitější, o čemž se lze velmi snadno přesvědčit.

Nejprve program přeložíme:

$ go build file_block_copy.go

A spustíme přes strace:

$ strace ./file_block_copy

S těmito výsledky:

execve("./file_block_copy", ["./file_block_copy"], [/* 53 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x5526b0)       = 0
sched_getaffinity(0, 8192, {f, 0, 0, 0}) = 32
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0985000000
mmap(0xc000000000, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc000000000
...
...
...
openat(AT_FDCWD, "test_input.txt", O_RDONLY|O_CLOEXEC) = 3
epoll_create1(EPOLL_CLOEXEC)            = 4
epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=2195279520, u64=139678826712736}}) = -1 EPERM (Operation not permitted)
epoll_ctl(4, EPOLL_CTL_DEL, 3, {0, {u32=0, u64=0}}) = -1 EPERM (Operation not permitted)
openat(AT_FDCWD, "output.txt", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 5
epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=2195279520, u64=139678826712736}}) = -1 EPERM (Operation not permitted)
epoll_ctl(4, EPOLL_CTL_DEL, 5, {0, {u32=0, u64=0}}) = -1 EPERM (Operation not permitted)
read(3, "line #1\nline #2\n", 16)       = 16
write(1, "read 16 bytes\n", 14)         = 14
write(5, "line #1\nline #2\n", 16)      = 16
write(1, "written 16 bytes\n", 17)      = 17
read(3, "line #3\nline #4\n", 16)       = 16
write(1, "read 16 bytes\n", 14)         = 14
write(5, "line #3\nline #4\n", 16)      = 16
write(1, "written 16 bytes\n", 17)      = 17
read(3, "line #5\n", 16)                = 8
write(1, "read 8 bytes\n", 13)          = 13
write(5, "line #5\n", 8)                = 8
write(1, "written 8 bytes\n", 16)       = 16
read(3, "", 16)                         = 0
write(1, "reached end of file\n", 20)   = 20
write(1, "Closing file 'output.txt'\n", 26) = 26
close(5)                                = 0
write(1, "Closing file 'test_input.txt'\n", 30) = 30
close(3)                                = 0
write(1, "Copied 40 bytes\n", 16)       = 16
write(1, "\n", 1)                       = 1
exit_group(0)                           = ?
+++ exited with 0 +++

Povšimněte si způsobu výpisu – u každého systémového volání jsou uvedeny parametry, a to v některých případech inteligentně – namísto číselné konstanty se používá symbolická konstanta PROT_READ atd. Taktéž se vypisuje návratová hodnota volání, a to opět (pokud je to ovšem možné) formou symbolické konstanty (ENOENT) popř. zprávy („No such file or directory“, „Operation not permitted“). Na konci můžeme vidět, že volání funkce fmt.Printf() se (po naformátování výstupního řetězce) provede formou write na standardní výstup (vrátí se přitom počet zapsaných/vytištěných bajtů).

Program používá tyto soubory:

Soubor # Význam
1 standardní výstup
3 vstupní soubor otevřený pro čtení
5 výstupní soubor otevřený pro zápis

8. Profiler určený pro ekosystém jazyka Go

Po popisu nástrojů ltrace a strace, které lze použít pro sledování činnosti prakticky jakékoli nativní aplikace, bez ohledu na to, v jakém programovacím jazyce byla původně vytvořena, se začneme zabývat technologiemi a nástroji určenými přímo pro programovací jazyk Go a ekosystém postavený okolo tohoto jazyka. V první řadě se jedná o takzvaný profiler, který je součástí standardní instalace Go a může ho tedy bez problémů využít jakýkoli vývojář. Profiler je možné spustit několika různými způsoby, ovšem nejjednodušší je jeho spuštění společně s (jednotkovými) testy, tj. při použití příkazu go test. Podrobnější informace o tom, jakým způsobem se spouští testy společně s profilerem, a jak se vybírá typ profileru, získáme příkazem:

$ go test --help

Z několikastránkové nápovědy lze zjistit, že existuje hned několik typů profilerů, a to konkrétně profiler pro zjištění pokrytí zdrojového kódu testy, klasický profiler zjišťující využití strojového času mikroprocesoru, další klasický profiler se zjištěním způsobu využití paměti a taktéž profiler s metrikami, které se týkají mutexů:

-coverprofile cover.out
    Write a coverage profile to the file after all tests have passed.
    Sets -cover.
 
-cpuprofile cpu.out
    Write a CPU profile to the specified file before exiting.
    Writes test binary as -c would.
 
-memprofile mem.out
    Write an allocation profile to the file after all tests have passed.
    Writes test binary as -c would.
 
-memprofilerate n
    Enable more precise (and expensive) memory allocation profiles by
    setting runtime.MemProfileRate. See 'go doc runtime.MemProfileRate'.
    To profile all memory allocations, use -test.memprofilerate=1.
 
-mutexprofile mutex.out
    Write a mutex contention profile to the specified file
    when all tests are complete.
    Writes test binary as -c would.

Povšimněte si, že se při zapnutí (povolení) každého profileru navíc určuje jméno zdrojového kódu, do kterého profiler zapisuje zjištěné informace a metriky. Pokud totiž spustíme testy s nějakým profilerem, bude výstup příkazu go test … stále stejný (žádné další informace se implicitně nezobrazí), ovšem všechna profilerem zjištěná data budou uložena do specifikovaného souboru, jehož obsah bude nutné zpracovat dalšími nástroji – ty jsou ovšem taktéž součástí standardní distribuce programovacího jazyka Go. Jinými slovy – soubory, které jsou výsledkem práce profilerů, se většinou nečtou přímo, ale je je nutné nějakým způsobem interpretovat.

Poznámka: seznam dalších nástrojů, které máme jako vývojáři používající programovací jazyk Go k dispozici, se ve skutečnosti získá velmi snadno:
$ go tool
 
addr2line
asm
buildid
cgo
compile
cover
dist
doc
fix
link
nm
objdump
pack
pprof
test2json
tour
trace
vet

Z těchto nástrojů nás dnes budou zajímat dva – pprof a trace. I pro tyto nástroje je pochopitelně možné zobrazit nápovědu:

$ go tool pprof --help
 
usage:
 
Produce output in the specified format.
 
   pprof  [options] [binary]  ...
 
Omit the format to get an interactive shell whose commands can be used
to generate various views of a profile
 
   pprof [options] [binary]  ...
 
Omit the format and provide the "-http" flag to get an interactive web
interface at the specified host:port that can be used to navigate through
various views of a profile.
 
   pprof -http [host]:[port] [options] [binary]  ...
...
...
...

a:

$ go tool trace
 
Usage of 'go tool trace':
Given a trace file produced by 'go test':
        go test -trace=trace.out pkg
 
Open a web browser displaying trace:
        go tool trace [flags] [pkg.test] trace.out
 
Generate a pprof-like profile from the trace:
    go tool trace -pprof=TYPE [pkg.test] trace.out
...
...
...

9. Demonstrační příklad pro otestování paměťového i CPU profileru

Příklad, na němž budeme sledovat činnost profilerů, bude velmi jednoduchý. Jedná se o projekt složený z několika modulů:

├── main.go
├── slices
│   └── slices.go
└── slices_test.go

Hlavní modul pouze volá funkci Slices() z balíčku slices:

package main
 
import "slices/slices"
 
func main() {
        slices.Slices()
}

V testu (přes něj budeme profiler spouštět) se opět volá stejná funkce, tentokrát jako součást testovacího scénáře:

package slices_test
 
import (
        "slices/slices"
        "testing"
)
 
func TestSlices(t *testing.T) {
        slices.Slices()
}

A nakonec se samozřejmě musíme podívat na implementaci funkce nazvané Slices. V ní je vytvořen řez nad prázdným polem a posléze se v programové smyčce do řezu přidávají další prvky (takže se interně provádí realokace pole, nad nímž je řez vytvořen):

package slices
 
import "fmt"
 
func Slices() {
        var a [0]int
        s := a[:]
 
        fmt.Println(s)
 
        for i := 1; i <= 100000; i++ {
                s = append(s, i)
        }
 
        fmt.Println(s)
}
Poznámka: samotný výsledek běhu programu nás vlastně nemusí prozatím zajímat.

10. Spuštění CPU profileru a základní analýza výsledků

CPU profiler je možné spustit současně s testy (v našem případě jediným testem), a to následujícím způsobem:

$ go test slices_test.go -cpuprofile=cprof

Po dokončení testu by se měl v pracovním adresáři objevit mj. i soubor „cprof“ obsahující měření získaná CPU profilerem. Výsledky si můžeme snadno zobrazit v textovém formátu:

$ go tool pprof -text cprof

Příklad výstupu:

File: slices.test
Type: cpu
Time: Jun 7, 2019 at 9:29pm (CEST)
Duration: 201.39ms, Total samples = 50ms (24.83%)
Showing nodes accounting for 50ms, 100% of 50ms total
      flat  flat%   sum%        cum   cum%
      20ms 40.00% 40.00%       40ms 80.00%  fmt.(*pp).handleMethods
      10ms 20.00% 60.00%       10ms 20.00%  runtime.(*itabTableType).find
      10ms 20.00% 80.00%       20ms 40.00%  runtime.getitab
      10ms 20.00%   100%       10ms 20.00%  runtime.memclrNoHeapPointers
         0     0%   100%       50ms   100%  command-line-arguments_test.TestSlices
         0     0%   100%       40ms 80.00%  fmt.(*pp).doPrintln
         0     0%   100%       10ms 20.00%  fmt.(*pp).free
         0     0%   100%       40ms 80.00%  fmt.(*pp).printArg
         0     0%   100%       40ms 80.00%  fmt.(*pp).printValue
         0     0%   100%       50ms   100%  fmt.Fprintln
         0     0%   100%       50ms   100%  fmt.Println
         0     0%   100%       10ms 20.00%  runtime.(*mcache).nextFree
         0     0%   100%       10ms 20.00%  runtime.(*mcache).nextFree.func1
         0     0%   100%       10ms 20.00%  runtime.(*mcache).refill
         0     0%   100%       10ms 20.00%  runtime.(*mcentral).cacheSpan
         0     0%   100%       10ms 20.00%  runtime.(*mcentral).grow
         0     0%   100%       20ms 40.00%  runtime.assertE2I2
         0     0%   100%       10ms 20.00%  runtime.heapBits.initSpan
         0     0%   100%       10ms 20.00%  runtime.makeslice
         0     0%   100%       10ms 20.00%  runtime.mallocgc
         0     0%   100%       10ms 20.00%  runtime.newArenaMayUnlock
         0     0%   100%       10ms 20.00%  runtime.newMarkBits
         0     0%   100%       10ms 20.00%  runtime.systemstack
         0     0%   100%       50ms   100%  slices/slices.Slices
         0     0%   100%       10ms 20.00%  sync.(*Pool).Put
         0     0%   100%       10ms 20.00%  sync.(*Pool).pin
         0     0%   100%       10ms 20.00%  sync.(*Pool).pinSlow
         0     0%   100%       50ms   100%  testing.tRunner

Vytvořit si můžeme i obrázek s výsledkem měření provedeného profilerem:

$ go tool pprof -png cprof

Obrázek 1: Výsledek měření provedeného CPU profilerem.

11. Spuštění paměťového profileru a základní analýza výsledků

Druhým typem profileru je paměťový profiler. I ten můžeme spustit společně s testy; postačuje pouze uvést jméno souboru, do kterého má profiler uložit naměřené informace:

$ go test slices_test.go -memprofile=mprof

Po dokončení běhu testů si můžeme zobrazit výsledek paměťového profileru, a to opět v čistě textové podobě:

$ go tool pprof -text mprof

Příklad výstupu, nyní s uvedením spotřeby operační paměti jednotlivými komponentami aplikace:

File: slices.test
Type: alloc_space
Time: Jun 7, 2019 at 9:31pm (CEST)
Showing nodes accounting for 7715.48kB, 100% of 7715.48kB total
      flat  flat%   sum%        cum   cum%
 4484.36kB 58.12% 58.12%  7715.48kB   100%  slices/slices.Slices
 1658.26kB 21.49% 79.61%  1658.26kB 21.49%  fmt.(*buffer).WriteByte (inline)
 1024.02kB 13.27% 92.89%  1024.02kB 13.27%  reflect.packEface
  548.84kB  7.11%   100%   548.84kB  7.11%  fmt.(*buffer).Write (inline)
         0     0%   100%  7715.48kB   100%  command-line-arguments_test.TestSlices
         0     0%   100%   548.84kB  7.11%  fmt.(*fmt).fmtInteger
         0     0%   100%   548.84kB  7.11%  fmt.(*fmt).pad
         0     0%   100%  3231.12kB 41.88%  fmt.(*pp).doPrintln
         0     0%   100%   548.84kB  7.11%  fmt.(*pp).fmtInteger
         0     0%   100%  3231.12kB 41.88%  fmt.(*pp).printArg
         0     0%   100%  3231.12kB 41.88%  fmt.(*pp).printValue
         0     0%   100%  3231.12kB 41.88%  fmt.Fprintln
         0     0%   100%  3231.12kB 41.88%  fmt.Println
         0     0%   100%  1024.02kB 13.27%  reflect.Value.Interface
         0     0%   100%  1024.02kB 13.27%  reflect.valueInterface
         0     0%   100%  7715.48kB   100%  testing.tRunner

I zde je možné vygenerovat výstup ve formě obrázku:

$ go tool pprof -png cprof

Obrázek 2: Výsledek měření provedeného CPU profilerem.

Spustit můžeme i HTTP server, který nám zobrazí výsledky práce profileru:

$ go tool pprof -http localhost:8080 mprof

Obrázek 3: Takzvaný „flame graph“ zobrazený ve webovém prohlížeči.

12. Úprava demonstračního příkladu: paměťově náročnější operace

Aby bylo možné lépe sledovat činnost správce operační paměti implementovaného v programovacím jazyce Go, předchozí demonstrační příklad nepatrně pozměníme – zvětšíme počet iterací smyčky, v níž se volá funkce append() na jeden milion iterací:

package slices2
 
import "fmt"
 
func Slices() {
        var a [0]int
        s := a[:]
 
        for i := 1; i <= 1000000; i++ {
                s = append(s, i)
        }
 
        fmt.Printf("Length: %d\n", len(s))
}
Poznámka: ostatní části projektu zůstanou zachovány, tj. jak hlavní modul, tak i modul s testy se nijak nezmění.

13. Analýza výsledků získaných profilery

Nyní již budou výsledky získané profilery vypadat zcela jinak. Pochopitelně se zvýší spotřeba operační paměti, a to na necelých 42 MB:

$ go tool pprof -text mprof
 
File: slices2.test
Type: alloc_space
Time: Jun 7, 2019 at 9:35pm (CEST)
Showing nodes accounting for 41.85MB, 100% of 41.85MB total
      flat  flat%   sum%        cum   cum%
   41.85MB   100%   100%    41.85MB   100%  slices2/slices2.Slices
         0     0%   100%    41.85MB   100%  command-line-arguments_test.TestSlices
         0     0%   100%    41.85MB   100%  testing.tRunner

Profiler měřící strojový čas strávený v jednotlivých funkcích ukáže zajímavou a vlastně i očekávanou věc – hodně času se stráví ve funkci pro přenos bloků paměti (runtime.memmove) při realokaci pole. Ovšem současně se zde objevují i další funkce, které jsme předtím neviděli – tyto funkce souvisejí s prací správce paměti a jeho části pro uvolňování paměti (garbage collector):

$ go tool pprof -text cprof
 
File: slices2.test
Type: cpu
Time: Jun 7, 2019 at 9:35pm (CEST)
Duration: 201.41ms, Total samples = 30ms (14.89%)
Showing nodes accounting for 30ms, 100% of 30ms total
      flat  flat%   sum%        cum   cum%
      10ms 33.33% 33.33%       10ms 33.33%  runtime.memmove
      10ms 33.33% 66.67%       10ms 33.33%  runtime.procyield
      10ms 33.33%   100%       20ms 66.67%  slices2/slices2.Slices
         0     0%   100%       20ms 66.67%  command-line-arguments_test.TestSlices
         0     0%   100%       10ms 33.33%  runtime.gcBgMarkWorker
         0     0%   100%       10ms 33.33%  runtime.gcBgMarkWorker.func2
         0     0%   100%       10ms 33.33%  runtime.gcDrain
         0     0%   100%       10ms 33.33%  runtime.growslice
         0     0%   100%       10ms 33.33%  runtime.markroot
         0     0%   100%       10ms 33.33%  runtime.markroot.func1
         0     0%   100%       10ms 33.33%  runtime.scang
         0     0%   100%       10ms 33.33%  runtime.systemstack
         0     0%   100%       20ms 66.67%  testing.tRunner

14. Podrobnější výsledky získané CPU profilerem

Podrobnější informace o využití CPU je možné získat různými způsoby. Použít můžeme například přepínač -traces s následujícím výsledkem:

$ go tool pprof -traces cprof
 
File: slices2.test
Type: cpu
Time: Jun 7, 2019 at 9:35pm (CEST)
Duration: 201.41ms, Total samples = 30ms (14.89%)
-----------+-------------------------------------------------------
      10ms   slices2/slices2.Slices
             command-line-arguments_test.TestSlices
             testing.tRunner
-----------+-------------------------------------------------------
      10ms   runtime.memmove
             runtime.growslice
             slices2/slices2.Slices
             command-line-arguments_test.TestSlices
             testing.tRunner
-----------+-------------------------------------------------------
      10ms   runtime.procyield
             runtime.scang
             runtime.markroot.func1
             runtime.markroot
             runtime.gcDrain
             runtime.gcBgMarkWorker.func2
             runtime.systemstack
             runtime.gcBgMarkWorker
-----------+-------------------------------------------------------

Ovšem neméně zajímavé je zjištění, že nyní v samostatné gorutině běžel i garbage collector, což je názorněji vidět z obrázku:

Obrázek 4: Paralelně běžící garbage collector (pravý sloupec).

Podobně si můžeme (v prohlížeči) zobrazit i flame graf:

Obrázek 5: Takzvaný „flame graph“ zobrazený ve webovém prohlížeči, nyní ve chvíli, kdy běžel garbage collector.

15. Demonstrační příklad využívající gorutiny

Další demonstrační příklad, na kterém si ověříme činnost profilerů, jsme již v tomto seriálu viděli. Jedná se o aplikaci, která po svém spuštění vypočítá a vykreslí Mandelbrotovu množinu, a to tak, že se každý obrazový řádek vypočítá v samostatné gorutině.

Struktura tohoto programu je následující (opět musíme mít k dispozici testy):

mandelbrot
├── mandelbrot.go
├── mandelbrot_test.go
└── renderer
    ├── mandelbrot.go
    └── palettes.go

Hlavní modul, který výpočet spustí:

package main
 
import (
        "fmt"
        "mandelbrot/renderer"
        "os"
        "strconv"
)
 
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)
        }
 
        renderer.Start(width, height, maxiter)
}

Modul obsahující pouze test, v němž se vykreslí Mandelbrotova množina v rozlišení 256×256 pixelů:

package renderer_test
 
import (
        "mandelbrot/renderer"
        "testing"
)
 
func TestRenderer(t *testing.T) {
        renderer.Start(256, 256, 255)
}

Samotná realizace výpočtu:

package renderer
 
import (
        "image"
        "image/png"
        "log"
        "os"
)
 
func writeImage(width uint, height uint, pixels []byte) {
        img := image.NewNRGBA(image.Rect(0, 0, int(width), int(height)))
        pixel := 0
 
        for y := 0; y < int(height); y++ {
                offset := img.PixOffset(0, y)
                for x := uint(0); x < width; x++ {
                        img.Pix[offset] = pixels[pixel]
                        img.Pix[offset+1] = pixels[pixel+1]
                        img.Pix[offset+2] = pixels[pixel+2]
                        img.Pix[offset+3] = 0xff
                        pixel += 3
                        offset += 4
                }
        }
 
        outputFile, err := os.Create("mandelbrot.png")
        if err != nil {
                log.Fatal(err)
        }
        defer outputFile.Close()
 
        png.Encode(outputFile, img)
}
 
func iterCount(cx float64, cy float64, maxiter uint) uint {
        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++
        }
        return i
}
 
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++ {
                i := iterCount(cx, cy, maxiter)
                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 Start(width int, height int, maxiter int) {
        done := make(chan bool, height)
 
        pixels := 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[:], pixels[offset:offset+delta], cy, done)
                offset += delta
                cy += 3.0 / float64(height)
        }
        for i := 0; i < height; i++ {
                <-done
        }
        writeImage(uint(width), uint(height), pixels)
}

Modul s barvovou paletou (nemá význam pro měření, ale pro hezčí obrázky):

package renderer

/* 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},
        ...
        ...
        ...
        {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}}

Obrázek 6: Výsledek činnosti výpočtu.

16. Výsledky měření CPU profileru

Nejprve spustíme CPU profiler společně s testy. Nejedná se o nic nového:

$ go test mandelbrot_test.go -cpuprofile=cprof

Zobrazení výsledků profileru:

$ go tool pprof -text cprof

Výstup:

File: renderer.test
Type: cpu
Time: Jun 7, 2019 at 9:14pm (CEST)
Duration: 201.36ms, Total samples = 60ms (29.80%)
Showing nodes accounting for 60ms, 100% of 60ms total
      flat  flat%   sum%        cum   cum%
      40ms 66.67% 66.67%       40ms 66.67%  mandelbrot/renderer.iterCount
      10ms 16.67% 83.33%       10ms 16.67%  compress/flate.(*compressor).findMatch
      10ms 16.67%   100%       10ms 16.67%  image/png.abs (inline)
         0     0%   100%       20ms 33.33%  command-line-arguments_test.TestRenderer
         0     0%   100%       10ms 16.67%  compress/flate.(*Writer).Write
         0     0%   100%       10ms 16.67%  compress/flate.(*compressor).deflate
         0     0%   100%       10ms 16.67%  compress/flate.(*compressor).write
         0     0%   100%       10ms 16.67%  compress/zlib.(*Writer).Write
         0     0%   100%       20ms 33.33%  image/png.(*Encoder).Encode
         0     0%   100%       20ms 33.33%  image/png.(*encoder).writeIDATs
         0     0%   100%       20ms 33.33%  image/png.(*encoder).writeImage
         0     0%   100%       20ms 33.33%  image/png.Encode
         0     0%   100%       10ms 16.67%  image/png.filter
         0     0%   100%       10ms 16.67%  image/png.paeth
         0     0%   100%       20ms 33.33%  mandelbrot/renderer.Start
         0     0%   100%       40ms 66.67%  mandelbrot/renderer.calcMandelbrot
         0     0%   100%       20ms 33.33%  mandelbrot/renderer.writeImage
         0     0%   100%       20ms 33.33%  testing.tRunner

Pravděpodobně nejzajímavější bude ale analýza funkce, kde program strávil nejvíce času. Jedná se o funkci mandelbrot/renderer.iterCount, takže si (ve webovém prohlížeči) zobrazme podrobněji, které výpočty byly nejsložitější:

Obrázek 7: Nejvíce času se strávilo ve vnitřní smyčce funkce mandelbrot/renderer.iterCount. To, o které řádky se jedná, nemusí být zcela přesné; záleží na optimalizacích atd.

17. Spuštění profileru přímo z vyvíjené aplikace

Prozatím jsme profiler spouštěli nepřímo přes testy, ovšem ve skutečnosti je možné profiler spustit přímo z vyvíjené aplikace. Vyžaduje to však úpravu zdrojového kódu, aby se otevřel soubor, do něhož bude profiler zapisovat výsledky a taktéž musíme profiler spustit:

package main
 
import (
        "fmt"
        "log"
        "mandelbrot/renderer"
        "os"
        "runtime/pprof"
        "strconv"
)
 
func main() {
        f, err := os.Create("mandelbrot2.prof")
        if err != nil {
                log.Fatalf("failed to create profiler output file: %v", err)
        }
        defer func() {
                if err := f.Close(); err != nil {
                        log.Fatalf("failed to close profiler file: %v", err)
                }
        }()
 
        if err := pprof.StartCPUProfile(f); err != nil {
                log.Fatalf("failed to start profle: %v", err)
        }
        defer pprof.StopCPUProfile()
 
        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)
        }
 
        renderer.Start(width, height, maxiter)
}

18.Podrobnější výsledky CPU profileru

Po spuštění předchozího upraveného příkladu se vytvoří soubor s výsledky měření profileru. Ty si zobrazíme nám již známým způsobem:

$ go tool pprof -traces mandelbrot2.prof
 
File: mandelbrot
Type: cpu
Time: Jun 10, 2019 at 10:27pm (CEST)
Duration: 201.45ms, Total samples = 60ms (29.78%)
-----------+-------------------------------------------------------
      10ms   mandelbrot/renderer.iterCount
             mandelbrot/renderer.calcMandelbrot
-----------+-------------------------------------------------------
      10ms   mandelbrot/renderer.iterCount
             mandelbrot/renderer.calcMandelbrot
-----------+-------------------------------------------------------
      20ms   mandelbrot/renderer.iterCount
             mandelbrot/renderer.calcMandelbrot
-----------+-------------------------------------------------------
      10ms   compress/flate.(*compressor).deflate
             compress/flate.(*compressor).write
             compress/flate.(*Writer).Write
             compress/zlib.(*Writer).Write
             image/png.(*encoder).writeImage
             image/png.(*encoder).writeIDATs
             image/png.(*Encoder).Encode
             image/png.Encode
             mandelbrot/renderer.writeImage
             mandelbrot/renderer.Start
             command-line-arguments_test.TestRenderer
             testing.tRunner
-----------+-------------------------------------------------------
      10ms   image/png.filter
             image/png.(*encoder).writeImage
             image/png.(*encoder).writeIDATs
             image/png.(*Encoder).Encode
             image/png.Encode
             mandelbrot/renderer.writeImage
             mandelbrot/renderer.Start
             command-line-arguments_test.TestRenderer
             testing.tRunner
-----------+-------------------------------------------------------

Obrázek 8: Grafické znázornění, ve kterých částech programu se strávilo nejvíce času.

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ě dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Projekt Popis projektu Cesta
1 clibs.go volání céčkových funkcí srand a rand https://github.com/tisnik/go-root/blob/master/article29/clibs.go
2 file_block_copy kopie obsahu souboru https://github.com/tisnik/go-root/blob/master/article29/fi­le_block_copy
3 slices použití funkce append pro přidávání prvků do řezu https://github.com/tisnik/go-root/blob/master/article29/slices
4 slices2 použití funkce append pro přidávání prvků do řezu https://github.com/tisnik/go-root/blob/master/article29/slices2
5 mandelbrot program pro vykreslení Mandelbrotovy množiny https://github.com/tisnik/go-root/blob/master/article29/man­delbrot/
6 mandelbrot2 program pro vykreslení Mandelbrotovy množiny se spuštěním profileru https://github.com/tisnik/go-root/blob/master/article29/man­delbrot2/

20. Odkazy na Internetu

  1. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  2. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  3. Package trace
    https://golang.org/pkg/runtime/trace/
  4. Introducing HTTP Tracing
    https://blog.golang.org/http-tracing
  5. Command trace
    https://golang.org/cmd/trace/
  6. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  7. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  8. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  9. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  10. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  11. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  12. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  13. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  14. Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/
  15. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  16. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  17. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  18. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  19. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  20. The LLDB Debugger
    http://lldb.llvm.org/
  21. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  22. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  23. Go is on a Trajectory to Become the Next Enterprise Programming Language
    https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e
  24. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  25. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  26. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  27. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  28. 10 tools written in Go that every developer needs to know
    https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/
  29. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  30. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  31. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  32. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  33. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  34. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  35. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  36. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  37. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  38. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  39. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  40. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  41. go-cron
    https://github.com/rk/go-cron
  42. gocron
    https://github.com/jasonlvhit/gocron
  43. clockwork
    https://github.com/whiteShtef/cloc­kwork
  44. clockwerk
    https://github.com/onatm/clockwerk
  45. JobRunner
    https://github.com/bamzi/jobrunner
  46. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  47. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  48. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  49. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  50. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  51. go-prompt
    https://github.com/c-bata/go-prompt
  52. readline
    https://github.com/chzyer/readline
  53. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  54. go-readline
    https://github.com/fiorix/go-readline
  55. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  56. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  57. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  58. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  59. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  60. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  61. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  62. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  63. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  64. Editline Library (libedit)
    http://thrysoee.dk/editline/
  65. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  66. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  67. WinEditLine
    http://mingweditline.sourceforge.net/
  68. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  69. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  70. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  71. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  72. history(3) – Linux man page
    https://linux.die.net/man/3/history
  73. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  74. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  75. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  76. Balíček ogletest
    https://github.com/jacobsa/ogletest
  77. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  78. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  79. package testing
    https://golang.org/pkg/testing/
  80. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  81. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  82. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  83. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  84. GoConvey
    http://goconvey.co/
  85. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  86. 5 simple tips and tricks for writing unit tests in #golang
    https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742
  87. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  88. package gg
    https://godoc.org/github.com/fo­gleman/gg
  89. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  90. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  91. The Go image package
    https://blog.golang.org/go-image-package
  92. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  93. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  94. YAML
    https://yaml.org/
  95. edn
    https://github.com/edn-format/edn
  96. Smile
    https://github.com/FasterXML/smile-format-specification
  97. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  98. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  99. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  100. Introducing JSON
    http://json.org/
  101. Package json
    https://golang.org/pkg/encoding/json/
  102. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  103. Go by Example: JSON
    https://gobyexample.com/json
  104. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  105. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  106. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  107. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  108. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  109. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  110. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  111. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  112. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  113. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  114. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  115. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  116. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  117. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  118. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  119. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  120. Algorithms to Go
    https://yourbasic.org/
  121. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  122. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/
  123. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  124. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  125. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  126. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  127. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  128. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  129. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  130. The Go Programming Language (home page)
    https://golang.org/
  131. GoDoc
    https://godoc.org/
  132. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  133. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  134. The Go Programming Language Specification
    https://golang.org/ref/spec
  135. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  136. Package builtin
    https://golang.org/pkg/builtin/
  137. Package fmt
    https://golang.org/pkg/fmt/
  138. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  139. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  140. Learning Go
    https://www.miek.nl/go/
  141. Go Bootcamp
    http://www.golangbootcamp.com/
  142. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  143. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  144. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  145. The Go Blog
    https://blog.golang.org/
  146. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  147. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  148. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  149. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  150. How the Go runtime implements maps efficiently (without generics)
    https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics
  151. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  152. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  153. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  154. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  155. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  156. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  157. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  158. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  159. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  160. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  161. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  162. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  163. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  164. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  165. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  166. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  167. Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
    https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/
  168. 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
    https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd
  169. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  170. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  171. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  172. Go vs. Python
    https://www.peterbe.com/plog/govspy
  173. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  174. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  175. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  176. Go by Example: Slices
    https://gobyexample.com/slices
  177. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  178. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  179. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  180. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  181. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  182. nils In Go
    https://go101.org/article/nil.html
  183. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  184. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  185. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  186. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  187. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  188. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  189. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  190. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  191. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  192. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  193. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  194. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  195. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  196. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  197. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  198. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  199. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  200. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  201. Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
    https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06
  202. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  203. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  204. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  205. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  206. Selectors
    https://golang.org/ref/spec#Selectors
  207. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  208. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  209. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  210. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  211. Part 21: Goroutines
    https://golangbot.com/goroutines/
  212. Part 22: Channels
    https://golangbot.com/channels/
  213. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  214. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  215. Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
    https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/
  216. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  217. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  218. Control Structures
    https://www.golang-book.com/books/intro/5
  219. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  220. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  221. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  222. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  223. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  224. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  225. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  226. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  227. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  228. Effective Go
    https://golang.org/doc/ef­fective_go.html
  229. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  230. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  231. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  232. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  233. Nils in Go
    https://www.doxsey.net/blog/nils-in-go