Understanding the Go runtime, Jesús Espino, Mattermost

Deep dive into Go's runtime mechanics, covering memory allocation, garbage collection, system initialization, and threading. Learn how Go manages resources behind the scenes.

Key takeaways
  • The Go runtime is included in all Go binaries and runs before the main function, handling critical initialization steps like memory allocation, garbage collection, and threading

  • The compiler works together with the runtime, generating calls to runtime functions for common operations like slice manipulation, map access, and goroutine creation

  • Memory allocation is handled through “mspans” - chunks of memory that group variables of the same size, with spans ranging from 8 bytes to 32 kilobytes

  • The garbage collector consists of three main components:

    • The main GC process handling mark and sweep
    • The sweeper helping with the sweep phase
    • The scavenger that returns memory to the operating system
  • System initialization follows these key steps:

    • Stop the world
    • Initialize thread local storage
    • Set up memory allocator
    • Initialize scheduler and processors
    • Start system monitor
    • Run init functions
    • Start main
  • The system monitor runs every 10ms to:

    • Check network operations
    • Detect slow syscalls
    • Force garbage collection when needed
    • Preempt long-running goroutines
  • For slow syscalls, Go detaches blocked OS threads from processors and creates new threads to maintain CPU utilization

  • The GC pacer determines when to run garbage collection based on memory usage, typically triggering when memory doubles from the previous GC

  • Each processor (P) represents a virtual CPU matching the number of cores or GOMAXPROCS setting

  • Go binaries are larger and slower to start compared to C programs due to runtime initialization, but this overhead becomes less significant in larger applications