> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Go High-Performance Programming EP9: Escape Analysis
- URL: https://huizhou92.com/go-high-performance-programming-ep9-escape-analysis/
- Published: 2024-08-15T08:33:07.000Z
- Updated: 2026-09-06T11:40:15.000Z
- Description: Go High-Performance Programming EP9: Escape Analysis. From the perspective of the Go compiler, memory allocation happens in two places: the stack and the h。
- Author: huizhou92
- Tags: tech, golang, go, #lang-en

From the perspective of the Go compiler, memory allocation happens in two places: the `stack` and the `heap`. For most developers, this difference doesn't matter much, as the Go compiler handles memory allocation automatically. However, from a performance standpoint, there is a significant difference between allocating memory on the stack versus the heap. If memory is allocated on the stack within a function, it is automatically reclaimed when the function completes. If it's allocated on the heap, it's managed by the garbage collector (GC), which involves a more complex process and can introduce performance overhead. This phenomenon of allocating memory on the heap when it could be on the stack is known as **escape**. To optimize performance, developers should minimize heap allocations.

> This article was first published in the Medium MPP plan. If you are a Medium user, please follow me on [Medium](https://medium.huizhou92.com/?ref=huizhou92.com). Thank you very much.

## Why Does Escape Happen?

The primary reasons for memory escape are simple: either the compiler cannot determine the variable's lifetime, or the stack does not have enough space for the required memory. The Go compiler uses **escape analysis** to decide whether a variable should be allocated on the stack or the heap. This process happens during the compilation stage and can be observed using the `-gcflags=-m` command to determine if a variable escapes to the heap.

## Impact of Escape on Performance

A straightforward benchmark test illustrates the impact of memory escaping. In the example below, `BenchmarkInt` uses a pointer array, causing escape with `&j`, whereas `BenchmarkInt2` does not cause `escape`.

```go
func BenchmarkInt(b *testing.B) {  
    for i := 0; i < b.N; i++ {  
        a := make([]*int, 100)  
        for j := 0; j < 100; j++ {  
            a[j] = &j  
        }  
    }  
}  
  
func BenchmarkInt2(b *testing.B) {  
    for i := 0; i < b.N; i++ {  
        a := make([]int, 100)  
        for j := 0; j < 100; j++ {  
            a[j] = j  
        }  
    }  
}

```

![Pasted image 20240815100606](https://images.hxzhouh.com/blog-images/2024/08/94407b8b1f8bdfe8bc0e6b43e0b5bba2.png)  
The results show that `BenchmarkInt2` is 30 times faster than `BenchmarkInt`, with no memory allocations, whereas `BenchmarkInt` has 101 memory allocations. This test highlights the importance of conducting escape analysis.

## Common Scenarios of Memory Escaping

### Variable and Pointer Escaping

When a variable's lifecycle extends beyond the function scope, the compiler allocates it on the heap, leading to **memory variable escape** or **pointer escape**.

```go
package main

import "fmt"

func main() {
    s := makeString()
    fmt.Println(s)
}

func makeString() *string {
    str := "Hello, World!"
    return &str
}

```

![Pasted image 20240814172956](https://images.hxzhouh.com/blog-images/2024/08/708bdbeab41177b6fadd170f5b90b698.png)

### Stack Overflow

The operating system limits the stack size used by kernel threads; on a 64-bit system, this limit is typically 8 MB. You can check the maximum allowed stack size with the `ulimit -a` command. The Go runtime dynamically allocates stack space as needed for goroutines, starting with an initial size of 2 KB. If local variables exceed a certain size, typically 64 KB, they escape to the heap. For instance, attempting to create an array of 8193 bytes:

```go
func main() {  
    a := make([]int64, 8176)  
    b := make([]int64, 8192)  
    c := make([]int64, 8193)  
    println(a, b, c)  
}

```

Arrays larger than 8192 bytes escape to the heap.  
![Pasted image 20240814173312](https://images.hxzhouh.com/blog-images/2024/08/dc03f59c65fffa7cc8268633f17d3dcf.png)

### Variables with Unknown Size

```go
func main() {  
    a := generate(3)  
    println(a)  
}  

func generate(n int) []int {  
    return make([]int, n)  
}

```

Since the size of `generate`'s parameter is determined at runtime, the compiler cannot allocate it on the stack, causing it to escape to the heap.  
![Pasted image 20240814193812](https://images.hxzhouh.com/blog-images/2024/08/f72a8c453543e57cda2915e85d092acd.png)

### `interface{}` Dynamic Type Escape

In Go, the empty interface `interface{}` can represent any type. If a function parameter is `interface{}`, the compiler cannot determine its type during compilation, leading to memory escape.

```go
func main() {  
    v := "Hello,World"  
    fmt.Printf("addr of v in bar = %p\n", &v)  
}

```

![Pasted image 20240815153912](https://images.hxzhouh.com/blog-images/2024/08/7d5c7815af15d4f254c1853da4f476f3.png)

Since `fmt.Printf` accepts parameters of type `any` (equivalent to `interface{}`), `v` escapes to the heap.

### Closure

In the following example, the function `Increase()` returns a closure that accesses the external variable `n`. Thus, `n` persists beyond the function's scope, causing it to escape to the heap.

```go
func Increase() func() int {  
    n := 0  
    return func() int {  
        n++  
        return n  
    }  
}  
  
func main() {  
    in := Increase()  
    fmt.Println(in()) // 1  
    fmt.Println(in()) // 2  
}

```

### Manually Avoiding Escapes

In the **`interface{}` Dynamic Type Escape** example, memory escape occurs even when simply printing "Hello, World". To prevent `v` from escaping, we can use the following function from the Go runtime code:

```go
// $GOROOT/src/runtime/stubs.go
func noescape(p unsafe.Pointer) unsafe.Pointer {
    x := uintptr(p)
    return unsafe.Pointer(x ^ 0) 
}

```

This function, widely used in the Go standard library and runtime, converts a pointer to a value and then back to a pointer, breaking the escape analysis data flow and preventing the pointer from escaping.  
![Pasted image 20240815154246](https://images.hxzhouh.com/blog-images/2024/08/7faddb58622ef13d6f16fd9b2a04b640.png)

```go
func noescape(p unsafe.Pointer) unsafe.Pointer {  
    x := uintptr(p)  
    return unsafe.Pointer(x ^ 0)  
}  
  
func main() {  
    v := "Hello,World"  
    v2 := "Hello,World1"  
    fmt.Printf("addr of v in bar = %p \n", (*int)(noescape(unsafe.Pointer(&v))))  
    fmt.Printf("addr of v in bar = %p\n", &v2) 
}

```

The output shows that `v` does not escape, while `v2` does.  
![Pasted image 20240815154140](https://images.hxzhouh.com/blog-images/2024/08/6dbceacbc3a1b5b3d2a306c6aee0c12d.png)

## Conclusion

In typical development scenarios, memory escape analysis is rarely a concern. Based on experience, optimizing a lock can yield better performance improvements than conducting multiple memory escape analyses. During regular development, remember:

- Passing values copies the entire object, while passing a pointer only copies the pointer address, pointing to the same object. Passing a pointer can reduce value copying but may lead to memory allocation escaping to the heap, increasing the garbage collection (GC) burden. The GC overhead from passing pointers can significantly impact performance in frequent object creation and deletion scenarios.  
**Generally, passing a pointer to modify original object values or larger struct objects would be best. Passing values offer better performance for read-only, smaller struct objects.**

## References

- [Five Things That Make Go Fast](https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast?ref=huizhou92.com)
- [ICSE20 Paper](http://www.wingtecher.com/themes/WingTecherResearch/assets/papers/ICSE20.pdf?ref=huizhou92.com)
- [Understanding Go Escape Analysis by Example](https://tonybai.com/2021/05/24/understand-go-escape-analysis-by-example/?ref=huizhou92.com)
- [Go's Hidden Pragmas](https://dave.cheney.net/2018/01/08/gos-hidden-pragmas?ref=huizhou92.com)