Pointer in Go
In Go gibt es eine grobe Faustregel: Stack Allokation ist schnell, Heap Allokation nicht so.
Wer schon mal C programmiert hat, kennt das. malloc
kostet Zeit. Der Stack wird magisch von
Go vorab angelegt und ist ein recht zusammenhängendes Stück Speicher. Heap liegt irgendwo im
Speicher.
“Und, was kümmert’s mich?” Meistens herzlich wenig. Go ist recht zügig und man sollte keinen obskuren Code schreiben, nur damit der Computer bei Laune gehalten wird. Am Ende des Tages, schreiben wir für Menschen, die den Code warten müssen.
Ein Beispiel
Ok, haben wir das so weit geklärt. Es gibt aber ein paar Muster, die in Go meist besseren Code produzieren und trotzdem lesbar sind. Gucken wir doch mal. Hier ein Code, den man etwa so in praktisch jeder Webanwendung sieht:
|
|
Die Funktion getPerson
macht irgendwelche Dantenbanksachen und je nachdem, ob was gefunden wurde, kommen nil
oder eben halt die gewünschten Daten zurück. Fein.
Was macht der Compiler damit?
$ go build -gcflags='-m' pointer_sample.go
(...)
./pointer_sample.go:18:9: &person{...} escapes to heap
Wie -gcflags='-m'
uns zeigt, wird person
auf dem Heap alloziert und damit der GC unnötig belastet.
Wir können auch - anders
Wenn wir das ganze jetzt minimal umschreiben, zB so:
|
|
haben wir einerseits den unschönen nil
Vergleich entfernt und…
$ go build -gcflags='-m' pointer_sample2.go
(...)
./pointer_sample2.go:19:16: p does not escape
wir befinden uns komplett auf dem Stack. Keine Arbeit für den GC.
Das ist auch ziemlich exakt die Strategie, die das io.Reader
Interface benutzt.
Fazit
Generell werden Pointer, die man nach unten reicht, meistens auf dem Stack alloziert. Reicht man eine Adresse nach oben - also von einer Funktion - wird sie auf dem Heap alloziert. Als Faustregel könnte man vielleicht sagen: Lege die Daten da an, wo du sie brauchst. Auch die SQL Interfaces befüllen nur Strukturen die man übergibt.
Nachtrag
Funktionen mit Interface Signatur, wie in diesem Fall Println
escapen übrigens immer die übergebenen
Parameter in den Heap. Damit kann auch ein mit if
deaktiviertes Logging die Performance beeinflussen,
einfach, weil der Compiler anderen Code generiert.
Oh, und das übliche Go Pattern für die Rückgabe von nicht vorhandenen Werten ist natürlich
ok, param := myFunc()
, was für nicht gefunden ein false und den Defaultwert des Typs zurückgibt.
By Value ist ziemlich lange schneller als by Pointer.