Just like C/C++, it's very straightforward to analyze memory usage in Go. But since Go compiler has GC and escape analysis, the memory layout looks a little complicate. Let's start from a simple code snippet.
package main
import (
"fmt"
)
type mem struct {
block [1024 * 1024]byte
}
type pmem *mem
var gmem *mem = new(mem)
func stackmem(m mem) {
fmt.Printf(" func local:%p", &m)
}
func main() {
const ITEMS = int(10)
mps := make([]pmem, ITEMS)
fmt.Printf("global: %p alloc for global:%p main:%p\n", &gmem, gmem, main)
for c := 0; c < ITEMS; c++ {
/*
var mp mem
fmt.Printf("local mp: %p", &mp)
go func(m *mem) {
fmt.Printf(" routine local:%p", &m)
}(&mp)
stackmem(mp)
*/
mps[c] = new(mem)
fmt.Printf(" local: %p alloc:%p\n", &mps[c], mps[c])
}
}
At first we commented out func and goroutine as above, the output would be:
F:\go\samples>go run mem.go
global: 0x5acad0 alloc for global:0xc08204a000 main:0x401040
local: 0xc08200c1e0 alloc:0xc08214c000
local: 0xc08200c1e8 alloc:0xc08224c000
local: 0xc08200c1f0 alloc:0xc08234c000
local: 0xc08200c1f8 alloc:0xc082456000
local: 0xc08200c200 alloc:0xc08255e000
local: 0xc08200c208 alloc:0xc082662000
local: 0xc08200c210 alloc:0xc08276e000
local: 0xc08200c218 alloc:0xc08286e000
local: 0xc08200c220 alloc:0xc08296e000
local: 0xc08200c228 alloc:0xc082a6e000
Obviously, as usual, the code/text/global segment layouts as OS loading at lower address space. the layout for stack/heap is not so clear, but for now, all new() returns continuous address space.
Let's comment out only the goroutine as below:
func main() {
const ITEMS = int(10)
mps := make([]pmem, ITEMS)
fmt.Printf("global: %p alloc for global:%p main:%p\n", &gmem, gmem, main)
for c := 0; c < ITEMS; c++ {
var mp mem
fmt.Printf("local mp: %p", &mp)
stackmem(mp)
/*
go func(m *mem) {
fmt.Printf(" routine local:%p", &m)
}(&mp)
*/
mps[c] = new(mem)
fmt.Printf(" local: %p alloc:%p\n", &mps[c], mps[c])
}
}
The output looks like:
F:\go\samples>go run mem.go
global: 0x5b9ad0 alloc for global:0xc08204a000 main:0x401140
local mp: 0xc08214a000 func local:0xc082544000 local: 0xc08200c1e0 alloc:0xc082644000
local mp: 0xc082756000 func local:0xc082864000 local: 0xc08200c1e8 alloc:0xc082544000
local mp: 0xc08214a000 func local:0xc082864000 local: 0xc08200c1f0 alloc:0xc08296c000
local mp: 0xc082a6c000 func local:0xc082864000 local: 0xc08200c1f8 alloc:0xc082754000
local mp: 0xc082b6c000 func local:0xc082c6c000 local: 0xc08200c200 alloc:0xc082a6c000
local mp: 0xc08285c000 func local:0xc08214a000 local: 0xc08200c208 alloc:0xc082d6c000
local mp: 0xc082e6c000 func local:0xc082f6c000 local: 0xc08200c210 alloc:0xc08306c000
local mp: 0xc08316c000 func local:0xc082f6c000 local: 0xc08200c218 alloc:0xc08285c000
local mp: 0xc08214a000 func local:0xc082b6c000 local: 0xc08200c220 alloc:0xc082c6c000
local mp: 0xc08326c000 func local:0xc08336c000 local: 0xc08200c228 alloc:0xc08316c000
Things look complicated now, it's not easy to discriminate stack/heap allocation just like C/C++. where heap/stack boundary is clear...