DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • How the Go Runtime Preempts Goroutines for Efficient Concurrency
  • Golang: Is It a Memory Leak?
  • Go 1.24+ Native FIPS Support for Easier Compliance
  • Comprehensive Guide to Property-Based Testing in Go: Principles and Implementation

Trending

  • Next Evolution in Integration: Architecting With Intent Using Model Context Protocol
  • How Kubernetes Cluster Sizing Affects Performance and Cost Efficiency in Cloud Deployments
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  • Memory-Optimized Tables: Implementation Strategies for SQL Server
  1. DZone
  2. Coding
  3. Languages
  4. Understanding Escape Analysis in Go

Understanding Escape Analysis in Go

Go uses escape analysis to decide whether values are stored in the stack or heap. Escape to heap happens when a value needs a longer lifespan or exceeds stack size.

By 
Pranoy Kundu user avatar
Pranoy Kundu
·
Mar. 22, 24 · Analysis
Likes (2)
Comment
Save
Tweet
Share
19.3K Views

Join the DZone community and get the full member experience.

Join For Free

Go uses escape analysis to determine the dynamic scope of Go values. Typically, go tries to store all the Go values in the function stack frame. The go compiler can predetermine which memory needs to be freed and emits machine instructions to clean it up. This way it becomes easy to clean up memory without the intervention of the Go Garbage Collector. This way of allocating memory is typically called stack allocation.

But when the compiler cannot determine the lifetime of a Go value it escapes to the heap. A value may also escape to the heap when the compiler does not know the size of the variable, or it’s too large to fit into the stack, or if the compiler cannot determine whether the variable is used after the function ends or the function stack frame is not used anymore.

Can we truly and completely know whether the value is stored in the heap or stack? The answer is NO. Only the compiler would know where exactly the value is stored all the time. As mentioned in this doc “The Go language takes responsibility for arranging the storage of Go values; in most cases, a Go developer need not care about where these values are stored, or why, if at all.”

There might still be scenarios where we might want to know the allocation to improve performance. As we know, physical memory is finite, and overuse might result in unnecessary performance issues.

Let’s now see how we can determine when and why a variable escapes to the heap. We will use the go build command to determine it. Run go help build to get various options for go build. We will use go build -gcflags=”-m” command to ask the compiler where the variables are being put. Let’s now go through some examples to determine it:

  1. In this example, we call a square function from our main function
Go
 
package main

func main() {
   x := 2
   square(x)
}

func square(x int) int {
   return x*x
}


When we run the above code with go build -gcflags=”-m” we get the below result:

Go
 
# github.com/pranoyk/escape-analysis
./main.go:8:6: can inline square
./main.go:3:6: can inline main
./main.go:5:8: inlining call to square


Right now everything is in the stack frame.

2. Let’s now modify our code to return a pointer from the square function

Go
 
package main

func main() {
   x := 2
   square(x)
}

func square(x int) *int {
   y := x*x
   return &y
}


When we build this code we get the following:

# github.com/pranoyk/escape-analysis
./main.go:8:6: can inline square
./main.go:3:6: can inline main
./main.go:5:8: inlining call to square
./main.go:9:2: moved to heap: y


Here the value `y` escaped to the heap. Now notice why this happened. The value of `y` has to prevail once the square function life cycle has finished and hence it escapes to the heap.

3. Let’s modify the above function. Let’s make our square function accept a pointer and not return a value.

Go
 
package main

func main() {
   x := 4
   square(&x)
}

func square(x *int)  {
   *x = *x**x
}


When we build the above code we get the following: 

Go
 
# github.com/pranoyk/escape-analysis
./main.go:8:6: can inline square
./main.go:3:6: can inline main
./main.go:5:8: inlining call to square
./main.go:8:13: x does not escape


Notice in the above function even though we are passing the pointer to the square the compiler mentions that the variable `x` does not escape. This is because the variable `x` is created in the main function stack frame which lives longer than the square function stack frame.

4. Let’s make one more modification to the above code. Let’s make our square function both accept and return a pointer.

Go
 
package main


func main() {
   x := 4
   square(&x)
}


func square(x *int) *int {
   y := *x**x
   return &y
}


The allocation of the above code is:

Go
 
# github.com/pranoyk/escape-analysis
./main.go:8:6: can inline square
./main.go:3:6: can inline main
./main.go:5:8: inlining call to square
./main.go:8:13: x does not escape
./main.go:9:2: moved to heap: y


Now notice carefully that the result of this code is a combination of examples 2 and 3. If we look even further closely we can say that sharing memory down from the main to another function typically stays on the stack and sharing memory up from a function to the main typically escapes to the heap. We can never be completely sure about it always because only the compiler would truly know where the value is stored. But this still gives some hint as to when an escape to the heap may occur.

Conclusion

  • Escape analysis in Go is a way in which the compiler determines whether the value is to be stored in the stack frame or the heap.
  • Anything that cannot be stored in the function stack frame escapes to the heap.
  • We can check the memory allocation of our code using `go build -gcflags=”-m”`.
  • Although, go manages the memory allocation quite efficiently and almost always a developer might not be concerned with it. It’s still good to know in case you want to improve on the performance.

References

  • Understanding Allocations: the Stack and the Heap 
  • A Guide to the Go Garbage Collector
garbage collection Go (programming language)

Published at DZone with permission of Pranoy Kundu. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How the Go Runtime Preempts Goroutines for Efficient Concurrency
  • Golang: Is It a Memory Leak?
  • Go 1.24+ Native FIPS Support for Easier Compliance
  • Comprehensive Guide to Property-Based Testing in Go: Principles and Implementation

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!