Obsah
1. Užitečné balíčky pro každodenní použití Go (2), porovnání výkonnosti Go s céčkem
2. Zpracování argumentů předaných na příkazové řádce
4. Celá čísla s neomezeným rozsahem
5. Výpočet faktoriálu pro téměř libovolné n
6. Čísla s plovoucí řádovou čárkou s neomezeným rozsahem a přesností
8. Předání dat na standardní vstup externích utilit
9. Čtení a vyhledávání proměnných prostředí
10. Získání základních informací o běžícím procesu a jeho prostředí
11. Kvalita překladače jazyka Go při optimalizacích
12. Vzorový benchmark naprogramovaný v ANSI C
13. První varianta benchmarku přímo přepsaná do Go
14. Vliv bufferu na rychlost dokončení benchmarku
15. Druhá verze benchmarku: použití bufferovaného výstupu
16. Třetí verze benchmarku: výpočet po jednotlivých obrazových řádcích
17. Finální verze benchmarku: využití kanálů a gorutin pro paralelní výpočty
18. Porovnání výsledků všech benchmarků
19. Repositář s demonstračními příklady
1. Užitečné balíčky pro každodenní použití Go (2), porovnání výkonnosti Go s céčkem
Dnešní díl seriálu o programovacím jazyku Go je rozdělen na dvě části. V první polovině se budeme převážně věnovat dalším balíčkům, které se vývojářům mohou hodit v každodenní praxi. Ve druhé části si ukážeme jeden benchmark zaměřený jak na optimalizaci počítaných programových smyček a výpočtů s hodnotami typu float64, tak i na základní vstupně-výstupní operace. Porovnáme si dvě varianty benchmarku naprogramovaných v ANSI C s několika implementacemi vytvořenými přímo v programovacím jazyku Go. Na závěr si ukážeme, jakým způsobem lze benchmark vytvořený v Go relativně snadno přepsat do paralelní podoby s využitím nám již známých gorutin a kanálů (což samozřejmě není příliš férové v porovnání s jednovláknovou céčkovskou implementací, ovšem „paralelizace“ v Go je – na rozdíl od programovacího jazyka C – velmi snadná a, což je neméně důležité, i poměrně přímočará).
2. Zpracování argumentů předaných na příkazové řádce
Programovací jazyk Go se mj. používá i pro tvorbu různých nástrojů spouštěných z příkazového řádku. Typicky se těmto nástrojům musí předávat nějaké argumenty, takže si dnes ukážeme, jakým způsobem se tyto argumenty přečtou a zpracují. Jen pro připomenutí si nejprve ukažme, jak se s argumenty předávanými na příkazovém řádku pracuje v programovacím jazyku C. Předpokládejme, že se aplikaci mají předat tři celočíselné argumenty:
int main(int argc, char **argv) { if (argc < 4) { puts("usage: ./mandelbrot width height maxiter"); return 1; } int width = atoi(argv[1]); int height = atoi(argv[2]); int maxiter = atoi(argv[3]); ... ... ...
Z předchozího příkladu je patrné, že v programovacím jazyku C se informace o případných argumentech zapsaných na příkazovém řádku předávají přímo do funkce main, která je vstupním bodem aplikace (je automaticky zavolána runtime systémem). V prvním parametru nazvaném argc je uložen celkový počet argumentů a druhý parametr pojmenovaný argv obsahuje ukazatel na pole řetězců s argumenty (řetězce jsou v poli taktéž představovány ukazateli typu char*, což je v céčku obvyklý způsob reprezentace řetězců). Důležité je, že v poli argv se v prvním prvku (s indexem 0) nachází název spuštěného programu. Díky tomu je možné „simulovat“ větší množství programů s různým chováním, které jsou ovšem uloženy ve stejném binárním souboru – postačuje spustit symbolický ukazatel na tento soubor, protože řetězec argv[0] bude obsahovat právě jméno symlinku (takto se například rozlišují příkazy vi, vim a vimdiff reprezentované jediným binárním souborem a dvěma symlinky).
V programovacím jazyku Go se s argumenty zapsanými na příkazový řádek aplikace pracuje poněkud odlišným způsobem. Všechny argumenty (včetně jména spuštěného programu) jsou uloženy do pole os.Args, což mj. znamená, že nejdříve musíme importovat balíček os. Pole os.Args je typu:
var Args []string
což z pohledu vývojáře značí, že s jeho prvky můžeme pracovat jako s běžnými řetězci. To je ostatně ukázáno i v dnešním prvním demonstračním příkladu, jehož zdrojový kód naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article10/01_cmdline_params.go. Po spuštění tohoto příkladu se nejdříve vypíše celkový počet argumentů a posléze i jejich hodnoty:
package main import ( "fmt" "os" ) func main() { fmt.Printf("Arguments: %d\n", len(os.Args)) for index, element := range os.Args { fmt.Printf("Argument #%d = %s\n", index, element) } }
Samozřejmě si ukážeme použití tohoto jednoduchého příkladu v praxi.
Spuštění bez argumentů:
$ go run 01_cmdline_params.go Arguments: 1 Argument #0 = /tmp/go-build447690084/b001/exe/01_cmdline_params
Spuštění s předáním tří argumentů:
$ go run 01_cmdline_params.go foo bar baz Arguments: 4 Argument #0 = /tmp/go-build652687078/b001/exe/01_cmdline_params Argument #1 = foo Argument #2 = bar Argument #3 = baz
3. Standardní balíček flag
Přímá práce s výše popsaným polem os.Args je v praxi vhodná jen ve chvíli, kdy aplikace akceptuje pouze několik argumentů, které jsou navíc umístěny na pevné pozici (například může první argument obsahovat jméno vstupního souboru a druhý argument jméno souboru výstupního). Pokud však má aplikace podporovat různé přepínače, pojmenované argumenty, argumenty s krátkou a dlouhou variantou zápisu (-h, –help) apod., tak je lepší namísto ručního procházení polem os.Args využít možností poskytovaných standardním balíčkem nazvaným flag. S tímto balíčkem se pracuje následujícím způsobem:
- Nejdříve se s využitím funkcí flag.InvVar, flag.UintVar, flag.BoolVar, flag.Float64Var atd. zaregistrují jména argumentů očekávaných na příkazové řádce, jejich typy (řetězec, celé číslo, logický přepínač, …), výchozí hodnoty a taktéž ukazatele na proměnné, které se mají naplnit skutečně zapsanými hodnotami argumentů.
- Posléze se zavolá funkce flag.Parse, která argumenty zadané na příkazové řádce zpracuje a naplní příslušné proměnné (z tohoto důvodu se registrují ukazatele na proměnné).
Ukažme si nyní jednoduché základní použití balíčku flag i s příslušným komentářem:
// deklarace běžné proměnné typu int var width int // specifikace, že na příkazovém řádku očekáváme argument se jménem width // hodnotou tohoto argumentu má být celé číslo s výchozí hodnotou 0 // po zpracování a převodu na celé číslo se má výsledek uložit do proměnné width flag.IntVar(&width, "width", 0, "image width") // vlastní zpracování argumentů předaných na příkazové řádce flag.Parse() // výpis výsledku fmt.Printf("width: %d\n", width)
Následuje poněkud obsáhlejší demonstrační příklad, v němž jsou specifikovány čtyři argumenty, z toho dva celočíselné, jeden je typu boolean (buď je použit či nikoli) a poslední argument může obsahovat libovolný řetězec:
package main import ( "flag" "fmt" ) func main() { var width int var height int var aa bool var output string flag.IntVar(&width, "width", 0, "image width") flag.IntVar(&height, "height", 0, "image height") flag.BoolVar(&aa, "aa", false, "enable antialiasing") flag.StringVar(&output, "output", "", "output file name") flag.Parse() fmt.Printf("width: %d\n", width) fmt.Printf("height: %d\n", height) fmt.Printf("antialiasing: %t\n", aa) fmt.Printf("output file name: %s\n", output) }
Spuštění tohoto příkladu bez argumentů:
$ go run 02_flags.go width: 0 height: 0 antialiasing: false output file name:
Spuštění příkladu s argumenty:
$ go run 02_flags.go -width=320 -height=200 -aa -output=xyzzy width: 320 height: 200 antialiasing: true output file name: xyzzy
Balíček flag dokonce dokáže automaticky vygenerovat nápovědu na základě zaregistrovaných argumentů:
$ go run 02_flags.go -h Usage of /tmp/go-build202978013/b001/exe/02_flags: -aa enable antialiasing -height int image height -output string output file name -width int image width exit status 2
Popř. můžeme použít dlouhou variantu –help:
$ go run 02_flags.go --help Usage of /tmp/go-build109677334/b001/exe/02_flags: -aa enable antialiasing -height int image height -output string output file name -width int image width exit status 2
Samozřejmě nám nic nebrání ve specifikaci argumentů s krátkým a současně i dlouhým jménem – prostě u obou argumentů uvedeme stejnou proměnnou, což je ukázáno v dnešním třetím demonstračním příkladu:
package main import ( "flag" "fmt" ) func main() { var width int var height int var aa bool var output string flag.IntVar(&width, "w", 0, "image width (shorthand)") flag.IntVar(&width, "width", 0, "image width") flag.IntVar(&height, "h", 0, "image height (shorthand)") flag.IntVar(&height, "height", 0, "image height") flag.BoolVar(&aa, "a", false, "enable antialiasing (shorthand)") flag.BoolVar(&aa, "antialias", false, "enable antialiasing") flag.StringVar(&output, "o", "", "output file name (shorthand)") flag.StringVar(&output, "output", "", "output file name") flag.Parse() fmt.Printf("width: %d\n", width) fmt.Printf("height: %d\n", height) fmt.Printf("antialiasing: %t\n", aa) fmt.Printf("output file name: %s\n", output) }
Krátké otestování možností:
$ go run 03_flag_shorthands.go width: 0 height: 0 antialiasing: false output file name:
Vygenerovaná nápověda:
$ go run 03_flag_shorthands.go --help Usage of /tmp/go-build543408348/b001/exe/03_flag_shorthands: -a enable antialiasing (shorthand) -antialias enable antialiasing -h int image height (shorthand) -height int image height -o string output file name (shorthand) -output string output file name -w int image width (shorthand) -width int image width exit status 2
Specifikace zkrácených parametrů:
$ go run 03_flag_shorthands.go -w=320 -h=240 -o=xyzzy -a width: 320 height: 240 antialiasing: true output file name: xyzzy
Alternativní způsob volání:
$ go run 03_flag_shorthands.go -w 320 -h 240 -o xyzzy -a width: 320 height: 240 antialiasing: true output file name: xyzzy
4. Celá čísla s neomezeným rozsahem
Připomeňme si, že v programovacím jazyku Go mají vývojáři k dispozici několik základních datových typů určených pro práci s celými čísly, ať již se znaménkem, nebo bez znaménka. Jedná se o tyto typy, přičemž int a uint jsou systémově závislé aliasy:
Identifikátor | Typ | Stručný popis |
---|---|---|
int | datový typ | odpovídá buď typu int32 nebo int64 |
int8 | datový typ | osmibitové celé číslo se znaménkem |
int16 | datový typ | šestnáctibitové celé číslo se znaménkem |
int32 | datový typ | 32bitové celé číslo se znaménkem |
int64 | datový typ | 64bitové celé číslo se znaménkem |
uint | datový typ | odpovídá buď typu uint32 nebo uint64 |
uint8 | datový typ | osmibitové celé číslo bez znaménka |
uint16 | datový typ | 16bitové celé číslo bez znaménka |
uint32 | datový typ | 32bitové celé číslo bez znaménka |
uint64 | datový typ | 64bitové celé číslo bez znaménka |
Práce s hodnotami těchto typů je většinou velmi rychlá, a to z toho důvodu, že současné mikroprocesory operaci s celými čísly typicky provedou přímo v aritmeticko-logické jednotce v několika strojových cyklech (které se navíc překrývají s dalšími instrukcemi díky instrukční pipeline). Ovšem mohou nastat situace, kdy nám ani rozsah typu int64 nebo uint64 nebude dostačovat. V tomto případě je možné využít možnosti poskytované standardním balíčkem math/big, v němž se mj. nachází i specifikace nového typu Int. Podívejme se nyní, jakým způsobem se budou počítat druhé mocniny dvojky, a to v libovolném rozsahu (ve skutečnosti jsme omezeni pamětí přiřazenou procesu popř. maximální velikosti zásobníku gorutiny, ovšem z praktického hlediska se o žádné reálné omezení nejedná). Povšimněte si, že namísto běžných operací = a * je nutné použít metody SetInt64 a Mul, protože současná verze programovacího jazyka Go neumožňuje přetížení operátorů:
package main import ( "fmt" . "math/big" ) func main() { var x Int var y Int x.SetInt64(1) y.SetInt64(2) for i := 1; i < 200; i++ { fmt.Println(x.Text(10)) x.Mul(&x, &y) } }
Výsledek po spuštění bude vypadat následovně:
1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 ... ... ... 6277101735386680763835789423207666416102355444464034512896 12554203470773361527671578846415332832204710888928069025792 25108406941546723055343157692830665664409421777856138051584 50216813883093446110686315385661331328818843555712276103168 100433627766186892221372630771322662657637687111424552206336 200867255532373784442745261542645325315275374222849104412672 401734511064747568885490523085290650630550748445698208825344
Z těchto výsledků můžeme vidět, že skutečně nejsme omezeni „pouze“ rozsahem 64bitových slov.
5. Výpočet faktoriálu pro téměř libovolné n
S celými čísly o prakticky neomezeném rozsahu můžeme provádět všechny základní aritmetické operace; pouze nesmíme zapomenout na to, že se nezapisují s využitím operátorů +, -, *, / a %, ale příslušnými metodami popsanými na stránce https://golang.org/pkg/math/big/#Int. Taktéž porovnání dvou hodnot se neprovádí standardní šesticí relačních operátorů, ale metodou Int.Cmp Pro úplnost si ukažme, jakým způsobem je možné implementovat funkci pro výpočet faktoriálu pro libovolné kladné n:
package main import ( "fmt" . "math/big" ) func factorial(n *Int) *Int { one := NewInt(1) if n.Cmp(NewInt(0)) <= 0 { return one } else { return one.Mul(n, factorial(one.Sub(n, one))) } } func main() { for n := int64(1); n < 80; n++ { f := factorial(NewInt(n)) fmt.Printf("%3d! = %s\n", n, f.Text(10)) } }
Podívejme se nyní na faktoriály od 1! do 79! (samozřejmě můžeme spočítat i faktoriál pro vyšší n, výstup je však již příliš dlouhý a nevleze se do šířky vyhrazené textu článku):
1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800 11! = 39916800 12! = 479001600 13! = 6227020800 14! = 87178291200 15! = 1307674368000 16! = 20922789888000 17! = 355687428096000 18! = 6402373705728000 19! = 121645100408832000 20! = 2432902008176640000 21! = 51090942171709440000 22! = 1124000727777607680000 23! = 25852016738884976640000 24! = 620448401733239439360000 25! = 15511210043330985984000000 26! = 403291461126605635584000000 27! = 10888869450418352160768000000 28! = 304888344611713860501504000000 29! = 8841761993739701954543616000000 30! = 265252859812191058636308480000000 31! = 8222838654177922817725562880000000 32! = 263130836933693530167218012160000000 33! = 8683317618811886495518194401280000000 34! = 295232799039604140847618609643520000000 35! = 10333147966386144929666651337523200000000 36! = 371993326789901217467999448150835200000000 37! = 13763753091226345046315979581580902400000000 38! = 523022617466601111760007224100074291200000000 39! = 20397882081197443358640281739902897356800000000 40! = 815915283247897734345611269596115894272000000000 41! = 33452526613163807108170062053440751665152000000000 42! = 1405006117752879898543142606244511569936384000000000 43! = 60415263063373835637355132068513997507264512000000000 44! = 2658271574788448768043625811014615890319638528000000000 45! = 119622220865480194561963161495657715064383733760000000000 46! = 5502622159812088949850305428800254892961651752960000000000 47! = 258623241511168180642964355153611979969197632389120000000000 48! = 12413915592536072670862289047373375038521486354677760000000000 49! = 608281864034267560872252163321295376887552831379210240000000000 50! = 30414093201713378043612608166064768844377641568960512000000000000 51! = 1551118753287382280224243016469303211063259720016986112000000000000 52! = 80658175170943878571660636856403766975289505440883277824000000000000 53! = 4274883284060025564298013753389399649690343788366813724672000000000000 54! = 230843697339241380472092742683027581083278564571807941132288000000000000 55! = 12696403353658275925965100847566516959580321051449436762275840000000000000 56! = 710998587804863451854045647463724949736497978881168458687447040000000000000 57! = 40526919504877216755680601905432322134980384796226602145184481280000000000000 58! = 2350561331282878571829474910515074683828862318181142924420699914240000000000000 59! = 138683118545689835737939019720389406345902876772687432540821294940160000000000000 60! = 8320987112741390144276341183223364380754172606361245952449277696409600000000000000 61! = 507580213877224798800856812176625227226004528988036003099405939480985600000000000000 62! = 31469973260387937525653122354950764088012280797258232192163168247821107200000000000000 63! = 1982608315404440064116146708361898137544773690227268628106279599612729753600000000000000 64! = 126886932185884164103433389335161480802865516174545192198801894375214704230400000000000000 65! = 8247650592082470666723170306785496252186258551345437492922123134388955774976000000000000000 66! = 544344939077443064003729240247842752644293064388798874532860126869671081148416000000000000000 67! = 36471110918188685288249859096605464427167635314049524593701628500267962436943872000000000000000 68! = 2480035542436830599600990418569171581047399201355367672371710738018221445712183296000000000000000 69! = 171122452428141311372468338881272839092270544893520369393648040923257279754140647424000000000000000 70! = 11978571669969891796072783721689098736458938142546425857555362864628009582789845319680000000000000000 71! = 850478588567862317521167644239926010288584608120796235886430763388588680378079017697280000000000000000 72! = 61234458376886086861524070385274672740778091784697328983823014963978384987221689274204160000000000000000 73! = 4470115461512684340891257138125051110076800700282905015819080092370422104067183317016903680000000000000000 74! = 330788544151938641225953028221253782145683251820934971170611926835411235700971565459250872320000000000000000 75! = 24809140811395398091946477116594033660926243886570122837795894512655842677572867409443815424000000000000000000 76! = 1885494701666050254987932260861146558230394535379329335672487982961844043495537923117729972224000000000000000000 77! = 145183092028285869634070784086308284983740379224208358846781574688061991349156420080065207861248000000000000000000 78! = 11324281178206297831457521158732046228731749579488251990048962825668835325234200766245086213177344000000000000000000 79! = 894618213078297528685144171539831652069808216779571907213868063227837990693501860533361810841010176000000000000000000
6. Čísla s plovoucí řádovou čárkou s neomezeným rozsahem a přesností
Opět si připomeňme, že v programovacím jazyku Go existují dva základní datové typy určené pro reprezentaci hodnot s plovoucí řádovou čárkou. Tyto typy se jmenují float32 a float64, což odpovídá céčkovským typům float a double. Typy float32 i float64 svými vlastnostmi odpovídají normě IEEE 754, z níž si uvedeme jen krátký výňatek:
Čísla s plovoucí řádovou čárkou jsou reprezentována třemi bitovými poli určenými pro uložení znaménka, exponentu a mantisy.
Podle bitové šířky čísel exp, bias a m se rozlišují základní (basic) a rozšířené (extended) formáty FP čísel; norma IEEE 754 (její původní verze) přitom explicitně zmiňuje dva základní formáty: jednoduchá přesnost (single, v Go pak float32) a dvojitá přesnost (double, v Go float64). Druhá verze normy IEEE 754–2008 již obsahuje specifikaci většího množství formátů; navíc došlo k přejmenování typů single a double na binary32 a binary64:
Oficiální jméno | Základní | Známo též jako | Znaménko | Exponent | Mantisa | Celkem | Decimálních číslic |
---|---|---|---|---|---|---|---|
binary16 | × | half precision | 1b | 5b | 10b | 16b | cca 3,3 |
binary32 | ✓ | single precision/float/float32 | 1b | 8b | 23b | 32b | cca 7,2 |
binary64 | ✓ | double precision/float64 | 1b | 11b | 52b | 64b | cca 15,9 |
binary128 | ✓ | quadruple precision | 1b | 15b | 112b | 128b | cca 34,0 |
binary256 | × | octuple precision | 1b | 19b | 236b | 256b | cca 71,3 |
Zajímat nás nyní bude typ označený v Go jménem float32. Jeho 32 bitů je rozděleno takto:
- 1 bit pro znaménko
- 8 bitů pro exponent
- 23 bitů pro mantisu
Exponent je přitom posunutý o hodnotu bias, která je nastavena na 127, protože je použit vztah:
bias=2eb-1-1
a po dosazení eb=8 (bitů) dostaneme:
bias=28–1-1=27-1=128–1=127
Vzorec pro vyjádření reálné hodnoty vypadá následovně:
Xsingle=(-1)s × 2exp-127 × m
Naproti tomu u typu float64 je každá hodnota reprezentována šedesáti čtyřmi bity rozdělenými následujícím způsobem:
- 1 bit pro znaménko
- 11 bitů pro exponent
- 52 bitů pro mantisu
Bitově vypadá rozdělení následovně:
bit | 63 | 62 … 52 | 51 … 0 |
---|---|---|---|
význam | s | exponent (11 bitů) | mantisa (52 bitů) |
Exponent je v tomto případě posunutý o hodnotu bias=2047 a vzorec pro výpočet reálné hodnoty vypadá takto:
Xdouble=(-1)s × 2exp-2047 × m
Přičemž hodnotu mantisy je možné pro normalizované hodnoty získat pomocí vztahu:
m=1+m51-1+m50-2+m49-3+…+m0-52
(mx představuje x-tý bit mantisy)
Rozsah hodnot ukládaných ve dvojité přesnosti je –1,7×10308..1,7×10308, nejmenší možná nenulová hodnota je rovna 2,2×10-308.
V případě, že nám nebude rozsah hodnot (tj. v podstatě počet bitů exponentu) či přesnost (de facto počet bitů mantisy) dostačovat, můžeme namísto toho použít datový typ Float z balíčku math/big. Způsob jeho použití je velmi podobný nám již známému typu Int, takže si ukažme způsob postupného výpočtu prvků řady 2-n. Povšimněte si, jakým způsobem se převádí hodnota Float na řetězec s využitím metody Text, které se předává jak požadovaný formát, tak i šířka výpisu:
package main import ( "fmt" . "math/big" ) func main() { x := NewFloat(1.0) y := NewFloat(0.5) for i := 1; i < 82; i++ { fmt.Println(x.Text('f', 80)) x.Mul(x, y) } }
Z výsledků je patrné, že nedochází k žádným zaokrouhlovacím ani jiným chybám (stačí odseknout část před desetinnou tečkou a spočítat výsledky s typem Int pro kontrolu):
1.00000000000000000000000000000000000000000000000000000000000000000000000000000000 0.50000000000000000000000000000000000000000000000000000000000000000000000000000000 0.25000000000000000000000000000000000000000000000000000000000000000000000000000000 0.12500000000000000000000000000000000000000000000000000000000000000000000000000000 0.06250000000000000000000000000000000000000000000000000000000000000000000000000000 0.03125000000000000000000000000000000000000000000000000000000000000000000000000000 0.01562500000000000000000000000000000000000000000000000000000000000000000000000000 0.00781250000000000000000000000000000000000000000000000000000000000000000000000000 ... ... ... 0.00000000000000000000010587911840678754238354031258495524525642395019531250000000 0.00000000000000000000005293955920339377119177015629247762262821197509765625000000 0.00000000000000000000002646977960169688559588507814623881131410598754882812500000 0.00000000000000000000001323488980084844279794253907311940565705299377441406250000 0.00000000000000000000000661744490042422139897126953655970282852649688720703125000 0.00000000000000000000000330872245021211069948563476827985141426324844360351562500 0.00000000000000000000000165436122510605534974281738413992570713162422180175781250 0.00000000000000000000000082718061255302767487140869206996285356581211090087890625
7. Spuštění externích utilit
Poměrně často se setkáme s požadavkem na to, aby se s aplikace naprogramované v jazyku Go spustila nějaká externí utilita. K tomuto účelu je možné použít funkci Command, kterou nalezneme v balíčku os/exec. Této funkci lze předat proměnný počet parametrů, přičemž prvním parametrem je jméno spouštěné utility a v dalších parametrech pak argumenty předávané na příkazovém řádku. V dalším příkladu je ukázáno, jakým způsobem je možné spustit známou utilitku date a získat její výstup; a to s kontrolou, zda případně nedošlo k nějaké chybě při spouštění či při běhu utility:
package main import ( "fmt" "os/exec" ) func main() { cmd := exec.Command("date", "--date=next Mon") out, err := cmd.Output() if err != nil { fmt.Println(err) } else { fmt.Printf("Next Monday: %s\n", out) } }
$ go run 07_exec.go Next Monday: Po úno 4 00:00:00 CET 2019
8. Předání dat na standardní vstup externích utilit
Poněkud složitější je situace ve chvíli, kdy potřebujeme externí utilitě předat nějaké informace přes standardní vstup. Příkladem může být požadavek na spuštění nástroje sort, který dokáže seřadit řádky ze standardního vstupu. Pro spuštění utility sort nyní použijeme příkaz StdinPipe, který by měl vrátit handle nově otevřeného standardního vstupu utility (určeného pro zápis z pohledu programu v Go) a případný kód chyby. Do otevřeného standardního vstupu se zapisují data přes funkci io.WriteString a nesmíme zapomenout na jeho uzavření. Následně již výstup z utility zpracujeme nám již známým způsobem:
package main import ( "fmt" "io" "os/exec" ) func main() { cmd := exec.Command("sort") stdin, err := cmd.StdinPipe() if err != nil { fmt.Println(err) } io.WriteString(stdin, "zzz\n") io.WriteString(stdin, "xyz\n") io.WriteString(stdin, "aaa\n") stdin.Close() out, err := cmd.Output() if err != nil { fmt.Println(err) } else { fmt.Printf("sorted input:\n%s\n", out) } }
Příklad výstupu programu:
$ go run 08_exec_stdin.go sorted input: aaa xyz zzz
V případě, že se volá externí utilita zpracovávající rozsáhlejší data, je korektnější použít jiný způsob zápisu, v němž se data utilitě předávají v paralelně běžící gorutině a současně se čte výstup produkovaný utilitou. Tímto způsobem lze utilitě předat prakticky libovolné množství dat. Jedno z nejjednodušších řešení tohoto problému je ukázáno v následujícím demonstračním příkladu:
package main import ( "fmt" "io" "os/exec" ) func main() { cmd := exec.Command("sort") stdin, err := cmd.StdinPipe() if err != nil { fmt.Println(err) } go func() { defer stdin.Close() io.WriteString(stdin, "zzz\n") io.WriteString(stdin, "xyz\n") io.WriteString(stdin, "aaa\n") }() out, err := cmd.Output() if err != nil { fmt.Println(err) } else { fmt.Printf("sorted input:\n%s\n", out) } }
Výstup, který získáme z předchozího příkladu, bude vypadat následovně:
$ go run 09_exec_stdin.go sorted input: aaa xyz zzz
9. Čtení a vyhledávání proměnných prostředí
Dalším často prováděnou operací je čtení a popř. i modifikace proměnných prostředí (environment variables). Pro přečtení nějaké proměnné použijte funkci os.LookupEnv, která vrací hodnotu proměnné a příznak, zda byla proměnná nalezena. Pokud budete potřebovat vypsat všechny proměnné prostředí, použijte funkci os.Environ – výslednou sekvencí lze iterovat s využitím konstrukce for-range:
package main import ( "fmt" "os" ) func main() { env_vars := os.Environ() for i, env_var := range env_vars { fmt.Printf("%02d\t%s\n", i, env_var) } term, found := os.LookupEnv("TERM") if found { fmt.Printf("\n\n\nSelected TERM = %s", term) } else { fmt.Printf("\n\n\nThe TERM environment variable is not set") } }
10. Získání základních informací o běžícím procesu a jeho prostředí
V závěru první části článku si ještě ukažme několik funkcí z balíčku os, které slouží pro získání základních informací o běžícím procesu (PID) a o jeho prostředí (PID rodičovského procesu, adresář pro uložení dočasných souborů, aktuální adresář):
package main import ( "fmt" "os" ) func main() { fmt.Printf("PID = %d\n", os.Getpid()) fmt.Printf("Parent PID = %d\n", os.Getppid()) fmt.Printf("Temp. directory = %s\n", os.TempDir()) cwd, err := os.Getwd() if err == nil { fmt.Printf("CWD = %s\n", cwd) } else { fmt.Printf("can not get CWD") } }
Výstup může vypadat následovně:
PID = 8841 Parent PID = 8782 Temp. directory = /tmp CWD = /home/tester/article_10
11. Kvalita překladače jazyka Go při optimalizacích
Ve druhé části článku se pokusíme porovnat kvalitu překladače programovacího jazyka Go (jeho standardní verze) s překladačem ANSI C. Jak sami uvidíte, je nutné při psaní benchmarků brát v úvahu i vlastnosti standardních knihoven obou jazyků, které mohou mít dosti odlišné chování.
Dnešní benchmark bude provádět výpočty s výpisem výsledku výpočtů na standardní výstup. Ten bude přesměrován do souboru, protože výsledkem výpočtů budou bitmapy v jednoduchém a současně i přenositelném formátu Portable Pixel Map (viz [1]). Samozřejmě je nutné si uvědomit, že i výpis hodnot na standardní výstup znamená nutnost volání knihovních funkcí a bude ovlivňovat čas výpočtu (jak uvidíme dále, tak mnohdy dosti význačně). Celý benchmark spočívá ve výpočtu barev pixelů Mandelbrotovy množiny, přičemž rozlišení výsledného rastrového obrázku i maximální počet iterací bude možné zvolit z příkazového řádku.
Obrázek 1: Výsledek benchmarku pro fraktál s rozlišením 512×512 pixelů.
12. Vzorový benchmark naprogramovaný v ANSI C
První varianta příkladu naprogramovaného v jazyku C vypadá následovně:
#include <stdlib.h> #include <stdio.h> #include "palette_mandmap.h" void calc_mandelbrot(unsigned int width, unsigned int height, unsigned int maxiter, unsigned char palette[][3]) { puts("P3"); printf("%d %d\n", width, height); puts("255"); double cy = -1.5; int y; for (y=0; y<height; y++) { double cx = -2.0; int x; for (x=0; x<width; x++) { double zx = 0.0; double zy = 0.0; unsigned int i = 0; while (i < maxiter) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } unsigned char *color = palette[i]; unsigned char r = *color++; unsigned char g = *color++; unsigned char b = *color; printf("%d %d %d\n", r, g, b); cx += 3.0/width; } cy += 3.0/height; } } int main(int argc, char **argv) { if (argc < 4) { puts("usage: ./mandelbrot width height maxiter"); return 1; } int width = atoi(argv[1]); int height = atoi(argv[2]); int maxiter = atoi(argv[3]); calc_mandelbrot(width, height, maxiter, palette); return 0; }
Překlad provedeme takto pomocí Makefile:
# Parametry prekladace. CFLAGS=-Wall -ansi -O9 -funroll-loops -march=native PROGNAME=mandelbrot all: $(PROGNAME) clean: rm *.o rm $(PROGNAME) # Pravidlo pro slinkovani vsech objektovych souboru a vytvoreni # vysledne spustitelne aplikace. $(PROGNAME): $(PROGNAME).o $(CC) -o $@ $(LDFLAGS) $< # Pravidlo pro preklad kazdeho zdrojoveho souboru do prislusneho # objektoveho souboru. %.o: %.c $(CC) $(CFLAGS) -c $< -o $@
Benchmark spustíme několikrát a budeme přitom měnit požadované rozlišení výsledné bitmapy s fraktálem:
sizes="16 24 32 48 64 96 128 192 256 384 512 768 1024 1536 2048 3072 4096" OUTFILE="c.times" PREFIX="mandelbrot" rm $OUTFILE for size in $sizes do echo $size echo -n "$size " >> $OUTFILE /usr/bin/time --output $OUTFILE --append --format "%e %M" ./mandelbrot $size $size 255 > "${PREFIX}_${size}_${size}.ppm" done
Výsledky po spuštění na stroji s procesorem i5 se čtyřmi jádry:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.00 |
7 | 128×128 | 0.00 |
8 | 192×192 | 0.01 |
9 | 256×256 | 0.03 |
10 | 384×384 | 0.06 |
11 | 512×512 | 0.11 |
12 | 768×768 | 0.25 |
13 | 1024×1024 | 0.44 |
14 | 1536×1536 | 1.01 |
15 | 2048×2048 | 1.78 |
16 | 3072×3072 | 4.03 |
17 | 4096×4096 | 7.11 |
Výsledky po spuštění na stroji s procesorem i7 se šesti jádry:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.00 |
7 | 128×128 | 0.00 |
8 | 192×192 | 0.01 |
9 | 256×256 | 0.02 |
10 | 384×384 | 0.04 |
11 | 512×512 | 0.07 |
12 | 768×768 | 0.17 |
13 | 1024×1024 | 0.30 |
14 | 1536×1536 | 0.67 |
15 | 2048×2048 | 1.20 |
16 | 3072×3072 | 2.70 |
17 | 4096×4096 | 4.83 |
13. První varianta benchmarku přímo přepsaná do Go
Předchozí benchmark si nejdříve přepíšeme do jazyka Go, a to bez jakýchkoli zásadnějších úprav. Výsledek by mohl vypadat například takto:
import ( "fmt" "os" "strconv" ) func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte) { fmt.Println("P3") fmt.Printf("%d %d\n", width, height) fmt.Println("255") var cy float64 = -1.5 for y := uint(0); y < height; y++ { var cx float64 = -2.0 for x := uint(0); x < width; x++ { var zx float64 = 0.0 var zy float64 = 0.0 var i uint = 0 for i < maxiter { zx2 := zx * zx zy2 := zy * zy if zx2+zy2 > 4.0 { break } zy = 2.0*zx*zy + cy zx = zx2 - zy2 + cx i++ } color := palette[i] r := color[0] g := color[1] b := color[2] fmt.Printf("%d %d %d\n", r, g, b) cx += 3.0 / float64(width) } cy += 3.0 / float64(height) } } 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) } calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:]) }
Časy běhu na počítači s procesorem i5 jsou znatelně horší, než v případě céčkové varianty:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.01 |
6 | 96×96 | 0.02 |
7 | 128×128 | 0.04 |
8 | 192×192 | 0.09 |
9 | 256×256 | 0.16 |
10 | 384×384 | 0.36 |
11 | 512×512 | 0.64 |
12 | 768×768 | 1.41 |
13 | 1024×1024 | 2.54 |
14 | 1536×1536 | 5.66 |
15 | 2048×2048 | 10.08 |
16 | 3072×3072 | 23.61 |
17 | 4096×4096 | 40.53 |
Totéž zhoršení uvidíme i u počítače s procesorem i7:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.01 |
7 | 128×128 | 0.02 |
8 | 192×192 | 0.05 |
9 | 256×256 | 0.08 |
10 | 384×384 | 0.19 |
11 | 512×512 | 0.34 |
12 | 768×768 | 0.77 |
13 | 1024×1024 | 1.36 |
14 | 1536×1536 | 3.08 |
15 | 2048×2048 | 5.55 |
16 | 3072×3072 | 12.41 |
17 | 4096×4096 | 21.95 |
14. Vliv bufferu na rychlost dokončení benchmarku
Při porovnání výsledků benchmarku naprogramovaného v céčku a přeloženého s povolením optimalizací překladače s benchmarkem vytvořeným v Go by se mohlo zdát, že překladač Go nedokáže provádět příliš dobré optimalizace. To sice může být pravda (přesvědčíme se o tom v dalších kapitolách), ovšem benchmark ve skutečnosti provádí i export vypočteného obrázku na standardní výstup a ukazuje se, že právě tato část je dosti kritická operace, protože v případě Go se používá jiná metoda bufferování, než je tomu v céčku.
V jazyku C se při použití standardního výstupu používá buffer, který ovšem můžeme s využitím funkce setvbuf přenastavit a dokonce i zakázat. Ukažme si vypnutí bufferingu. Nejprve vytvoříme buffer s nulovou kapacitou (což je nyní ve skutečnosti zbytečné, ovšem později si můžete sami vyzkoušet změnit konstantu BUFFER_SIZE):
#define BUFFER_SIZE 0 char buffer[BUFFER_SIZE];
A posléze funkcí setvbuf řekneme, že se má buffer zcela vypnout (_IONBF), a to konkrétně pro soubor stdout (protože z pohledu céčka je standardní výstup běžným souborem, který je automaticky otevřený již při vstupu do funkce main):
setvbuf(stdout, buffer, _IONBF, BUFFER_SIZE);
Po této nepatrné úpravě se časy běhu céčkovského programu zcela změní. Výsledky běhu benchmarku ve chvíli, kdy je buffer vypnutý a má navíc nulovou délku:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.01 |
6 | 96×96 | 0.02 |
7 | 128×128 | 0.03 |
8 | 192×192 | 0.08 |
9 | 256×256 | 0.13 |
10 | 384×384 | 0.29 |
11 | 512×512 | 0.52 |
12 | 768×768 | 1.18 |
13 | 1024×1024 | 2.14 |
14 | 1536×1536 | 4.91 |
15 | 2048×2048 | 8.52 |
16 | 3072×3072 | 19.66 |
17 | 4096×4096 | 34.40 |
Nic nám samozřejmě nebrání provést i opačné nastavení – deklarovat obrovský buffer o kapacitě jednoho megabajtu a nastavit takzvané plné bufferování, bez závislosti na tom, zda se na výstup posílají znaky pro konce řádků či nikoli (tento typ bufferování by nám vadil při sledování standardního výstupu aplikace, my ovšem provádíme přesměrování do souboru):
#define BUFFER_SIZE 1*1024*1024 char buffer[BUFFER_SIZE];
Pro plné bufferování se použije konstanta _IOFBF:
setvbuf(stdout, buffer, _IOFBF, BUFFER_SIZE);
Nyní již budou výsledky benchmarku markantně odlišné od výsledků předchozích, což je ostatně patrné i z následující tabulky:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.01 |
7 | 128×128 | 0.01 |
8 | 192×192 | 0.01 |
9 | 256×256 | 0.03 |
10 | 384×384 | 0.06 |
11 | 512×512 | 0.11 |
12 | 768×768 | 0.25 |
13 | 1024×1024 | 0.44 |
14 | 1536×1536 | 1.00 |
15 | 2048×2048 | 1.75 |
16 | 3072×3072 | 3.99 |
17 | 4096×4096 | 7.10 |
15. Druhá verze benchmarku: použití bufferovaného výstupu
V programovacím jazyku Go se režim bufferování při zápisu do souborů řídí jiným způsobem, než je tomu v programovacím jazyku C. Musíme použít standardní balíček pojmenovaný bufio a v něm vytvořit novou instanci tzv. writeru, kterému se předá reference na otevřený soubor, u něhož chceme bufferování použít. Tímto souborem bude v našem případě os.Stdout. Nesmíme zapomenout na to, aby se na konci zápisu provedla operace Writer.Flush(), která zapíše celý zbytek bufferu na konec souboru:
w := bufio.NewWriter(os.Stdout) defer w.Flush()
Zápisy nyní nebudou prováděny přímo na os.Stdout funkcemi fmt.Println či fmt.Printf, ale budeme muset explicitně specifikovat soubor, do něhož se zápis má provést pomocí fmt.Fprintln a fmt.Fprintf:
fmt.Fprintln(w, "P3") fmt.Fprintf(w, "%d %d\n", width, height) fmt.Fprintln(w, "255") ... ... ... r := color[0] g := color[1] b := color[2] fmt.Fprintf(w, "%d %d %d\n", r, g, b)
Druhá varianta zdrojového kódu benchmarku bude vypadat takto:
package main import ( "bufio" "fmt" "os" "strconv" ) func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte) { w := bufio.NewWriter(os.Stdout) defer w.Flush() fmt.Fprintln(w, "P3") fmt.Fprintf(w, "%d %d\n", width, height) fmt.Fprintln(w, "255") var cy float64 = -1.5 for y := uint(0); y < height; y++ { var cx float64 = -2.0 for x := uint(0); x < width; x++ { var zx float64 = 0.0 var zy float64 = 0.0 var i uint = 0 for i < maxiter { zx2 := zx * zx zy2 := zy * zy if zx2+zy2 > 4.0 { break } zy = 2.0*zx*zy + cy zx = zx2 - zy2 + cx i++ } color := palette[i] r := color[0] g := color[1] b := color[2] fmt.Fprintf(w, "%d %d %d\n", r, g, b) cx += 3.0 / float64(width) } cy += 3.0 / float64(height) } } 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) } calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:]) }
Při pohledu na následující tabulku je zřejmé, že se nám podařilo přiblížení k výsledkům, které jsme získali z céčkovského kódu.
Průběh na počítači s procesorem i5:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.00 |
7 | 128×128 | 0.01 |
8 | 192×192 | 0.02 |
9 | 256×256 | 0.03 |
10 | 384×384 | 0.07 |
11 | 512×512 | 0.13 |
12 | 768×768 | 0.29 |
13 | 1024×1024 | 0.50 |
14 | 1536×1536 | 1.14 |
15 | 2048×2048 | 2.03 |
16 | 3072×3072 | 4.56 |
17 | 4096×4096 | 8.07 |
Průběh na počítači s procesorem i7:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.00 |
7 | 128×128 | 0.00 |
8 | 192×192 | 0.01 |
9 | 256×256 | 0.02 |
10 | 384×384 | 0.05 |
11 | 512×512 | 0.09 |
12 | 768×768 | 0.20 |
13 | 1024×1024 | 0.35 |
14 | 1536×1536 | 0.80 |
15 | 2048×2048 | 1.42 |
16 | 3072×3072 | 3.21 |
17 | 4096×4096 | 5.78 |
16. Třetí verze benchmarku: výpočet po jednotlivých obrazových řádcích
Předchozí benchmark si ještě dále upravíme, a to takovým způsobem, že dojde k oddělení kódu určeného pro zápis výsledného obrázku na standardní výstup od kódu pro výpočet. To ovšem není všechno, protože funkci pro výpočet Mandelbrotovy množiny změníme takovým způsobem, že se vypočte pouze jediný obrazový řádek (důvod pro tuto na první pohled možná podivnou změnu je vysvětlen v navazující kapitole). Změny budou vypadat takto:
Zápis (či lépe řečeno export) výsledné bitmapy je realizován v samostatné funkci pojmenované writeImage, které se předají rozměry bitmapy a hodnoty jednotlivých pixelů v poli typu []byte:
func writeImage(width uint, height uint, image []byte) { }
Funkce pro výpočet jediného řádku obrázku Mandelbrotovy množiny bude mít hlavičku:
func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte, image []byte, cy float64) { }
Povšimněte si, že v posledním parametru předáváme hodnotu cy, která určuje imaginární složku komplexního čísla C, které vstupuje do iterativního výpočtu Mandelbrotovy množiny (viz odkazovaný článek s podrobnějším vysvětlením).
Volání této funkce a postupné skládání bitmapy tedy může vypadat například takto:
image := make([]byte, width*height*3) offset := 0 delta := width * 3 var cy float64 = -1.5 for y := 0; y < height; y++ { calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:], image[offset:offset+delta], cy) offset += delta cy += 3.0 / float64(height) }
Úplný zdrojový kód benchmarku se tedy modifikuje následujícím způsobem:
package main import ( "bufio" "fmt" "os" "strconv" ) func writeImage(width uint, height uint, image []byte) { w := bufio.NewWriter(os.Stdout) defer w.Flush() fmt.Fprintln(w, "P3") fmt.Fprintf(w, "%d %d\n", width, height) fmt.Fprintln(w, "255") for i := 0; i < len(image); { r := image[i] i++ g := image[i] i++ b := image[i] i++ fmt.Fprintf(w, "%d %d %d\n", r, g, b) } } func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte, image []byte, cy float64) { var cx float64 = -2.0 for x := uint(0); x < width; x++ { var zx float64 = 0.0 var zy float64 = 0.0 var i uint = 0 for i < maxiter { zx2 := zx * zx zy2 := zy * zy if zx2+zy2 > 4.0 { break } zy = 2.0*zx*zy + cy zx = zx2 - zy2 + cx i++ } color := palette[i] image[3*x] = color[0] image[3*x+1] = color[1] image[3*x+2] = color[2] cx += 3.0 / float64(width) } } func main() { if len(os.Args) < 4 { println("usage: ./mandelbrot width height maxiter") os.Exit(1) } width, err := strconv.Atoi(os.Args[1]) if err != nil { fmt.Printf("Improper width parameter: '%s'\n", os.Args[1]) os.Exit(1) } height, err := strconv.Atoi(os.Args[2]) if err != nil { fmt.Printf("Improper height parameter: '%s'\n", os.Args[2]) os.Exit(1) } maxiter, err := strconv.Atoi(os.Args[3]) if err != nil { fmt.Printf("Improper maxiter parameter: '%s'\n", os.Args[3]) os.Exit(1) } image := make([]byte, width*height*3) offset := 0 delta := width * 3 var cy float64 = -1.5 for y := 0; y < height; y++ { calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:], image[offset:offset+delta], cy) offset += delta cy += 3.0 / float64(height) } writeImage(uint(width), uint(height), image) }
Povšimněte si, že se tyto úpravy – které zvyšují složitost programu – vlastně nijak zásadně neprojevily na výsledcích benchmarku:
Průběh na počítači s procesorem i5:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.00 |
7 | 128×128 | 0.01 |
8 | 192×192 | 0.02 |
9 | 256×256 | 0.04 |
10 | 384×384 | 0.07 |
11 | 512×512 | 0.13 |
12 | 768×768 | 0.29 |
13 | 1024×1024 | 0.50 |
14 | 1536×1536 | 1.19 |
15 | 2048×2048 | 2.09 |
16 | 3072×3072 | 4.73 |
17 | 4096×4096 | 8.44 |
Průběh na počítači s procesorem i7:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.00 |
7 | 128×128 | 0.00 |
8 | 192×192 | 0.01 |
9 | 256×256 | 0.02 |
10 | 384×384 | 0.05 |
11 | 512×512 | 0.09 |
12 | 768×768 | 0.20 |
13 | 1024×1024 | 0.35 |
14 | 1536×1536 | 0.82 |
15 | 2048×2048 | 1.49 |
16 | 3072×3072 | 3.25 |
17 | 4096×4096 | 5.74 |
17. Finální verze benchmarku: využití kanálů a gorutin pro paralelní výpočty
Nyní konečně nastal čas, abychom si vysvětlili, proč jsme vlastně upravili předchozí variantu benchmarku takovým způsobem, že je možné počítat jednotlivé obrazové řádky explicitním zavoláním funkce calcMandelbrot. Celý výpočet nyní budeme paralelizovat – každý obrazový řádek bude vypočten v samostatné gorutině. Vzhledem k tomu, že gorutiny jsou interně reprezentovány úsporným způsobem, je možné tuto optimalizaci bez problémů provést a vytvořit jich tak i několik tisíc (v závislosti na rozlišení bitmapy).
O to, aby se počkalo na dokončení všech gorutin, se postará kanál pojmenovaný done, jehož kapacita přesně odpovídá počtu gorutin. Nejprve se kanál vytvoří, následně se všechny gorutiny spustí a na konci čtením z kanálu počkáme na dokončení všech height gorutin:
done := make(chan bool, height) // na tomto místě bude umístěn vlastní výpočet for i := 0; i < height; i++ { <-done }
Jedinou další úpravou bude modifikace funkce calcMandelbrot, které se musí předat reference na kanál a do kterého se na konci výpočtu zapíše hodnota true (důležitý je zápis, nikoli vlastní hodnota):
func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte, image []byte, cy float64, done chan bool) { ... ... ... done <- true }
Poslední „paralelizovaná“ varianta benchmarku bude vypadat takto:
package main import ( "bufio" "fmt" "os" "strconv" ) func writeImage(width uint, height uint, image []byte) { w := bufio.NewWriter(os.Stdout) defer w.Flush() fmt.Fprintln(w, "P3") fmt.Fprintf(w, "%d %d\n", width, height) fmt.Fprintln(w, "255") for i := 0; i < len(image); { r := image[i] i++ g := image[i] i++ b := image[i] i++ fmt.Fprintf(w, "%d %d %d\n", r, g, b) } } func calcMandelbrot(width uint, height uint, maxiter uint, palette [][3]byte, image []byte, cy float64, done chan bool) { var cx float64 = -2.0 for x := uint(0); x < width; x++ { var zx float64 = 0.0 var zy float64 = 0.0 var i uint = 0 for i < maxiter { zx2 := zx * zx zy2 := zy * zy if zx2+zy2 > 4.0 { break } zy = 2.0*zx*zy + cy zx = zx2 - zy2 + cx i++ } color := palette[i] image[3*x] = color[0] image[3*x+1] = color[1] image[3*x+2] = color[2] cx += 3.0 / float64(width) } done <- true } func main() { if len(os.Args) < 4 { println("usage: ./mandelbrot width height maxiter") os.Exit(1) } width, err := strconv.Atoi(os.Args[1]) if err != nil { fmt.Printf("Improper width parameter: '%s'\n", os.Args[1]) os.Exit(1) } height, err := strconv.Atoi(os.Args[2]) if err != nil { fmt.Printf("Improper height parameter: '%s'\n", os.Args[2]) os.Exit(1) } maxiter, err := strconv.Atoi(os.Args[3]) if err != nil { fmt.Printf("Improper maxiter parameter: '%s'\n", os.Args[3]) os.Exit(1) } done := make(chan bool, height) image := make([]byte, width*height*3) offset := 0 delta := width * 3 var cy float64 = -1.5 for y := 0; y < height; y++ { go calcMandelbrot(uint(width), uint(height), uint(maxiter), mandmap[:], image[offset:offset+delta], cy, done) offset += delta cy += 3.0 / float64(height) } for i := 0; i < height; i++ { <-done } writeImage(uint(width), uint(height), image) }
Výsledky benchmarku budou podle očekávání mnohem lepší, a to z toho důvodu, že plně využijeme možnosti vícejádrových mikroprocesorů.
Průběh na počítači s procesorem i5:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.00 |
7 | 128×128 | 0.01 |
8 | 192×192 | 0.01 |
9 | 256×256 | 0.02 |
10 | 384×384 | 0.05 |
11 | 512×512 | 0.08 |
12 | 768×768 | 0.19 |
13 | 1024×1024 | 0.34 |
14 | 1536×1536 | 0.76 |
15 | 2048×2048 | 1.33 |
16 | 3072×3072 | 2.98 |
17 | 4096×4096 | 5.31 |
Průběh na počítači s procesorem i7:
# | Rozlišení | Čas (s) |
---|---|---|
1 | 16×16 | 0.00 |
2 | 24×24 | 0.00 |
3 | 32×32 | 0.00 |
4 | 48×48 | 0.00 |
5 | 64×64 | 0.00 |
6 | 96×96 | 0.00 |
7 | 128×128 | 0.00 |
8 | 192×192 | 0.00 |
9 | 256×256 | 0.01 |
10 | 384×384 | 0.03 |
11 | 512×512 | 0.04 |
12 | 768×768 | 0.10 |
13 | 1024×1024 | 0.19 |
14 | 1536×1536 | 0.42 |
15 | 2048×2048 | 0.73 |
16 | 3072×3072 | 1.66 |
17 | 4096×4096 | 2.94 |
18. Porovnání výsledků všech benchmarků
Na závěr si výsledky jednotlivých benchmarků porovnáme. Povšimněte si, v případě kódu běžícího v jednom vláknu je lepší překladač jazyka C (s optimalizacemi!), ovšem – což asi očekáváte – varianta s gorutinami je rychlejší, než nejlepší céčková varianta.
Počítač s procesorem i5 (čtyři jádra):
# | Rozlišení | C std. | C bez bufferu | C s bufferem | Go std. | Go s bufferem | Go po řádcích | Go s gorutinami |
---|---|---|---|---|---|---|---|---|
1 | 16×16 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 |
2 | 24×24 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 |
3 | 32×32 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 |
4 | 48×48 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 |
5 | 64×64 | 0,00 | 0,01 | 0,00 | 0,01 | 0,00 | 0,00 | 0,00 |
6 | 96×96 | 0,00 | 0,02 | 0,01 | 0,02 | 0,00 | 0,00 | 0,00 |
7 | 128×128 | 0,00 | 0,03 | 0,01 | 0,04 | 0,01 | 0,01 | 0,01 |
8 | 192×192 | 0,01 | 0,08 | 0,01 | 0,09 | 0,02 | 0,02 | 0,01 |
9 | 256×256 | 0,03 | 0,13 | 0,03 | 0,16 | 0,03 | 0,04 | 0,02 |
10 | 384×384 | 0,06 | 0,29 | 0,06 | 0,36 | 0,07 | 0,07 | 0,05 |
11 | 512×512 | 0,11 | 0,52 | 0,11 | 0,64 | 0,13 | 0,13 | 0,08 |
12 | 768×768 | 0,25 | 1,18 | 0,25 | 1,41 | 0,29 | 0,29 | 0,19 |
13 | 1024×1024 | 0,44 | 2,14 | 0,44 | 2,54 | 0,50 | 0,50 | 0,34 |
14 | 1536×1536 | 1,01 | 4,91 | 1,00 | 5,66 | 1,14 | 1,19 | 0,76 |
15 | 2048×2048 | 1,78 | 8,52 | 1,75 | 10,08 | 2,03 | 2,09 | 1,33 |
16 | 3072×3072 | 4,03 | 19,66 | 3,99 | 23,61 | 4,56 | 4,73 | 2,98 |
17 | 4096×4096 | 7,11 | 34,40 | 7,10 | 40,53 | 8,07 | 8,44 | 5,31 |
Počítač s procesorem i7 (šest jader):
# | Rozlišení | C std. | C bez bufferu | C s bufferem | Go std. | Go s bufferem | Go po řádcích | Go s gorutinami |
---|---|---|---|---|---|---|---|---|
1 | 16×16 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 |
2 | 24×24 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 |
3 | 32×32 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 |
4 | 48×48 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 |
5 | 64×64 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 | 0,00 |
6 | 96×96 | 0,00 | 0,01 | 0,00 | 0,01 | 0,00 | 0,00 | 0,00 |
7 | 128×128 | 0,00 | 0,02 | 0,00 | 0,02 | 0,00 | 0,00 | 0,00 |
8 | 192×192 | 0,01 | 0,04 | 0,01 | 0,05 | 0,01 | 0,01 | 0,00 |
9 | 256×256 | 0,02 | 0,07 | 0,02 | 0,08 | 0,02 | 0,02 | 0,01 |
10 | 384×384 | 0,04 | 0,16 | 0,04 | 0,19 | 0,05 | 0,05 | 0,03 |
11 | 512×512 | 0,07 | 0,29 | 0,07 | 0,34 | 0,09 | 0,09 | 0,04 |
12 | 768×768 | 0,17 | 0,65 | 0,17 | 0,77 | 0,20 | 0,20 | 0,10 |
13 | 1024×1024 | 0,30 | 1,20 | 0,30 | 1,36 | 0,35 | 0,35 | 0,19 |
14 | 1536×1536 | 0,67 | 2,64 | 0,67 | 3,08 | 0,80 | 0,82 | 0,42 |
15 | 2048×2048 | 1,20 | 4,67 | 1,19 | 5,55 | 1,42 | 1,49 | 0,73 |
16 | 3072×3072 | 2,70 | 10,67 | 2,68 | 12,41 | 3,21 | 3,25 | 1,66 |
17 | 4096×4096 | 4,83 | 18,84 | 4,89 | 21,95 | 5,78 | 5,74 | 2,94 |
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes popsaný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á doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Mandelbrot set (Rosetta code)
https://rosettacode.org/wiki/Mandelbrot_set - Computer language Benchmark Game: Mandelbrot set
https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/mandelbrot.html - 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 - 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