Obsah
1. Trasování a profilování aplikací naprogramovaných v Go
2. Standardní nástroje, které nejsou specifické pouze pro jazyk Go
3. Utilita ltrace – výpis informací o volaných knihovních funkcích
4. Příklad použití utility ltrace i pro aplikace psané v Go
5. Další užitečné možnosti nabízené nástrojem ltrace
6. Utilita strace – výpis informací o volaných systémových funkcích
7. Příklad použití utility strace pro aplikaci kopírující souboru
8. Profiler určený pro ekosystém jazyka Go
9. Demonstrační příklad pro otestování paměťovéh i CPU profileru
10. Spuštění CPU profileru a základní analýza výsledků
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
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ů:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
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) } }
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.
$ 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) +++
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
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 strace i ltrace 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.
$ 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) }
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)) }
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/file_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/mandelbrot/ |
6 | mandelbrot2 | program pro vykreslení Mandelbrotovy množiny se spuštěním profileru | https://github.com/tisnik/go-root/blob/master/article29/mandelbrot2/ |
20. Odkazy na Internetu
- Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Package trace
https://golang.org/pkg/runtime/trace/ - Introducing HTTP Tracing
https://blog.golang.org/http-tracing - Command trace
https://golang.org/cmd/trace/ - A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - 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/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - 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 - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 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/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 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 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - 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/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - 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 - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - 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/ - 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 - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - 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 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - 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/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go