This section covers some of the optimisations that the Go compiler performs.

are all handled in the front end of the compiler, while the code is still in its AST form; then the code is passed to the SSA compiler for further optimisation.

A year later, Go 1.7 introduced a new compiler backend based on SSA techniques replaced the previous Plan 9 style code generation. This new backend introduced many opportunities for generic and architecture specific optimistions.

In 2015 the then Go 1.5 compiler was mechanically translated from C into Go .

The Go compiler started as a fork of the Plan9 compiler tool chain circa 2007. The compiler at that time bore a strong resemblance to Aho and Ullman’s Dragon Book .

The first optimisation we’re doing to discuss is escape analysis.

To illustrate what escape analysis does recall that the Go spec does not mention the heap or the stack. It only mentions that the language is garbage collected in the introduction, and gives no hints as to how this is to be achieved.

A compliant Go implementation of the Go spec could store every allocation on the heap. That would put a lot of pressure on the the garbage collector, but it is in no way incorrect — for several years, gccgo had very limited support for escape analysis so could effectively be considered to be operating in this mode.

However, a goroutine’s stack exists as a cheap place to store local variables; there is no need to garbage collect things on the stack. Therefore, where it is safe to do so, an allocation placed on the stack will be more efficient.

In some languages, for example C and C++, the choice of allocating on the stack or on the heap is a manual exercise for the programmer—​heap allocations are made with malloc and free , stack allocation is via alloca . Mistakes using these mechanisms are a common cause of memory corruption bugs.

In Go, the compiler automatically moves a value to the heap if it lives beyond the lifetime of the function call. It is said that the value escapes to the heap.

type Foo struct { a , b , c , d int } func NewFoo () * Foo { return & Foo { a : 3 , b : 1 , c : 4 , d : 7 } }

In this example the Foo allocated in NewFoo will be moved to the heap so its contents remain valid after NewFoo has returned.

This has been present since the earliest days of Go. It isn’t so much an optimisation as an automatic correctness feature. Accidentally returning the address of a stack allocated variable is not possible in Go.

But the compiler can also do the opposite; it can find things which would be assumed to be allocated on the heap, and move them to stack.

Let’s have a look at an example

func Sum () int { const count = 100 numbers := make ([] int , count ) for i := range numbers { numbers [ i ] = i + 1 } var sum int for _ , i := range numbers { sum += i } return sum } func main () { answer := Sum () fmt . Println ( answer ) }

Sum adds the `int`s between 1 and 100 and returns the result.

Because the numbers slice is only referenced inside Sum , the compiler will arrange to store the 100 integers for that slice on the stack, rather than the heap. There is no need to garbage collect numbers , it is automatically freed when Sum returns.

4.2.1. Prove it! To print the compilers escape analysis decisions, use the -m flag. % go build -gcflags=-m examples/esc/sum.go # command-line-arguments examples/esc/sum.go:22:13: inlining call to fmt.Println examples/esc/sum.go:8:17: Sum make([]int, count) does not escape examples/esc/sum.go:22:13: answer escapes to heap examples/esc/sum.go:22:13: io.Writer(os.Stdout) escapes to heap examples/esc/sum.go:22:13: main []interface {} literal does not escape <autogenerated>:1: os.(*File).close .this does not escape Line 8 shows the compiler has correctly deduced that the result of make([]int, 100) does not escape to the heap. The reason it did no The reason line 22 reports that answer escapes to the heap is fmt.Println is a variadic function. The parameters to a variadic function are boxed into a slice, in this case a []interface{} , so answer is placed into a interface value because it is referenced by the call to fmt.Println . Since Go 1.6 the garbage collector requires all values passed via an interface to be pointers, what the compiler sees is approximately: var answer = Sum() fmt.Println([]interface{&answer}...) We can confirm this using the -gcflags="-m -m" flag. Which returns % go build -gcflags='-m -m' examples/esc/sum.go 2>&1 | grep sum.go:22 examples/esc/sum.go:22:13: inlining call to fmt.Println func(...interface {}) (int, error) { return fmt.Fprintln(io.Writer(os.Stdout), fmt.a...) } examples/esc/sum.go:22:13: answer escapes to heap examples/esc/sum.go:22:13: from ~arg0 (assign-pair) at examples/esc/sum.go:22:13 examples/esc/sum.go:22:13: io.Writer(os.Stdout) escapes to heap examples/esc/sum.go:22:13: from io.Writer(os.Stdout) (passed to call[argument escapes]) at examples/esc/sum.go:22:13 examples/esc/sum.go:22:13: main []interface {} literal does not escape In short, don’t worry about line 22, its not important to this discussion.

4.2.2. Exercises Does this optimisation hold true for all values of count ?

Does this optimisation hold true if count is a variable, not a constant?

Does this optimisation hold true if count is a parameter to Sum ?