Day 3: Malloc
🍂 Mood: Happily enjoying my first pumpkin flavored items of the year.
🎵 Soundtrack: Instrumental Study Playlist
📚 Reading Github Issues
I started my day by reading the 11 new github issues that had been created since yesterday. Two caught my eye.
-
The first was about http/2, which is a topic I have spent a lot of time thinking about for Cloud Foundry. This issue linked to a previously opened issue about max concurrent streams. Changes have been submited relating this issue, but I think it might be fun in the future to reproduce the error and more closely examine the proposed fix.
-
The second issue that caught my eye was about malloc. This caught my eye because I first saw the phrase “malloc” a few days ago when reading Julia Evans’ post “Patterns in Confusing Explanations”. This is what started my rabbit hole for today.
🐰 Malloc
Based on the name I assumed that malloc had something to do with memory allocation… and I was right!
🎥 I ended up watching the following videos:
- Understanding malloc by Jonathan Sprinkle
- Pointers and dynamic memory - stack vs heap by mycodeschool
- GopherCon UK 2018: Andre Carvalho - Understanding Go’s Memory Allocator
👀 Lower level topics like this make me wonder: did everyone else learn this stuff in college?
Things I learned
malloc
is a C function used to allocate memory on the heap.- There is a also a
malloc.go
file in the runtime package with great comments about how memory allocation is done in golang. - Processes do not read directly from physical memory. This is for security reasons and coordination reasons.
- Each process’ memory is divided up into pages (each page is usually 4kb).
- Each process has a page table, which maps its virtual pages to physical memory
frames.
-
Andre’s talk showed how to demonstrate that each process has its own virtual memory space, which I duplicated.
package main import ( "fmt" "math/rand" "time" ) func main() { f() } func f() { // make a random integer rand.Seed(time.Now().UnixNano()) i := rand.Intn(100) // print the int and its memory address fmt.Printf("%v at %p\n", i, &i) for { // don't let the program exit } }
- Then run this program in multiple terminals at the same time
$ ./main 57 at 0xc00001c0b8 // in another terminal $ ./main 77 at 0xc00001c0b8
- Look! Different integers use the same memory address at the same time! But they aren’t really the same memory address. Just the same address in different virtual memory spaces.
-
- Andre also talked about Thread-Caching Malloc (TCMalloc), which was originally implemented by Google for C. It is what the Go runtime allocator is based off of. It was super fun to think about algorithms in a way that I haven’t had to since I was interviewing for jobs. It’s cool to think that some programers actually get to think about this!
- I also duplicated some things Andrew did to see the actions the compiler is
doing regarding stack vs heap.
- In this example I make a pointer and show that it is allocated to the heap!
// main.go package main func main() { f() } func f() *int { i := 10 return &i }
Then run:
$ go build -gcflags "-m " main.go # command-line-arguments ./main.go:7:6: can inline f ./main.go:3:6: can inline main ./main.go:4:3: inlining call to f ./main.go:8:2: moved to heap: i 👈👈👈👈
- i is on the heap!!
- if you return just an int, you will see it is not moved to the heap
// main.go package main func main() { f() } func f() int { i := 10 return i }
Then run:
$ go build -gcflags "-m -m" main.go # command-line-arguments ./main.go:7:6: can inline f with cost 7 as: func() int { i := 10; return i } ./main.go:3:6: can inline main with cost 9 as: func() { f() } ./main.go:4:3: inlining call to f func() int { i := 10; return i } 👈👈👈👈 // nothing about heap here
- In this example I make a pointer and show that it is allocated to the heap!
💾 My Attempt to Remember the Memory Allocation in Go
- Your program asks for 20 B of memory.
- There are three different algorithms based on the size of memory requested:
- tiny allocations < 16 B
- small allocations < 32 kB
- large allocations > 32 kB
- 30 B is a “small” allocation.
- Go has 66 small size classes.
- Go rounds up the amount of memory requested to the nearest size class. The nearest size class for 20 B is class 3, which is 24 B.
- Go first checks in the process’
mcache
to see if it has enough memory of the right size to give our program. This does not need a lock because it is scoped to the process. Themcache
for each process has amspan
for each size class. These spans can allocate objects for their particular size class. Go looks to see if the span for class 3 is full.- If it is not full, then the span returns an object for the process to use. Done!
- If it is full, then the
mcache
has to get more memory frommcentral
.
mcentral
does a similar search to see if it has available memory of the correct size. Sincemcentral
is shared between threads it requires a lock to access.- If
mcentral
does not have the correct sized memory then it recurses to theCentral Heap
. - If
Central Heap
does not have the correct sized memory then it requests the correct size from the OS. - I’m skipping lots of facinating details about the data structures used at each level.
🧐 Now that I am writing this, I can’t remember why there are so many recursions
here. Why does there need to be an mcentral
and a central heap
? It’s my
EOD, so that sounds like a question for tomorrow! 😬 Hopefully these ramblings
will make sense to me tomorrow. At least enough so for me to pick up where I
left off.
🐰🕳 Meta Thoughts on Rabbit Holes
I am not usually a person who follows rabbit holes. I typically favor a “good enough” approach in development and debugging. For example, I don’t feel the need to know why a version of XYZ dependancy is causing memory issues. If you tell me it is, I am happy to bump the dependancy without the details. I am usually interested in understanding the layer below where I work, but I don’t need to know 3 layers below. I am happy to trust that those layers are working properly.
But that is not what this sabbatical is for! This sabbatical is for learning, so I am trying to explore the questions that come to me. I think that learning more about any part of golang or programming in general will only be a good thing.