🍂 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.

  1. 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.

  2. 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:

👀 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
      

💾 My Attempt to Remember the Memory Allocation in Go

  1. Your program asks for 20 B of memory.
  2. There are three different algorithms based on the size of memory requested:
    • tiny allocations < 16 B
    • small allocations < 32 kB
    • large allocations > 32 kB
  3. 30 B is a “small” allocation.
  4. Go has 66 small size classes.
  5. 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.
  6. 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. The mcache for each process has a mspan 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.
    1. If it is not full, then the span returns an object for the process to use. Done!
    2. If it is full, then the mcache has to get more memory from mcentral.
  7. mcentral does a similar search to see if it has available memory of the correct size. Since mcentral is shared between threads it requires a lock to access.
  8. If mcentral does not have the correct sized memory then it recurses to the Central Heap.
  9. If Central Heap does not have the correct sized memory then it requests the correct size from the OS.
  10. 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.