Skip to content

Instantly share code, notes, and snippets.

@upbit
Created May 7, 2020 02:53
Show Gist options
  • Save upbit/dc00a4ef900c8f98cf902f4072948c6b to your computer and use it in GitHub Desktop.
Save upbit/dc00a4ef900c8f98cf902f4072948c6b to your computer and use it in GitHub Desktop.
GC pause for maps
package main
import (
"fmt"
"os"
"runtime"
"time"
)
// Results of this program on my machine: (macos, go 1.14):
//
// for t in 1 2 3 4 5; do go run maps.go $t; done
//
// Higher parallelism does help, to some extent:
//
// for t in 1 2 3 4 5; do GOMAXPROCS=8 go run maps.go $t; done
//
// Output(go 1.14):
// With map[int32]*int32, GC took 456.159324ms
// With map[int32]int32, GC took 10.644116ms
// With map shards ([]map[int32]*int32), GC took 383.296446ms
// With map shards ([]map[int32]int32), GC took 1.023655ms
// With a plain slice ([]main.t), GC took 172.776µs
//
// Output(go 1.9):
// With map[int32]*int32, GC took 457.036664ms
// With map[int32]int32, GC took 22.160147ms
// With map shards ([]map[int32]*int32), GC took 432.721998ms
// With map shards ([]map[int32]int32), GC took 3.887943ms
// With a plain slice ([]main.t), GC took 80.547µs
func main() {
const N = 5e7 // 5000w
if len(os.Args) != 2 {
fmt.Printf("usage: %s [1 2 3 4]\n(number selects the test)\n", os.Args[0])
return
}
switch os.Args[1] {
case "1":
// Big map with a pointer in the value
m := make(map[int32]*int32)
for i := 0; i < N; i++ {
n := int32(i)
m[n] = &n
}
runtime.GC()
fmt.Printf("With %T, GC took %s\n", m, timeGC())
_ = m[0] // Preserve m until here, hopefully
case "2":
// Big map, no pointer in the value
m := make(map[int32]int32)
for i := 0; i < N; i++ {
n := int32(i)
m[n] = n
}
runtime.GC()
fmt.Printf("With %T, GC took %s\n", m, timeGC())
_ = m[0]
case "3":
// Split the map into 100 shards
shards := make([]map[int32]*int32, 100)
for i := range shards {
shards[i] = make(map[int32]*int32)
}
for i := 0; i < N; i++ {
n := int32(i)
shards[i%100][n] = &n
}
runtime.GC()
fmt.Printf("With map shards (%T), GC took %s\n", shards, timeGC())
_ = shards[0][0]
case "4":
// Split the map into 100 shards
shards := make([]map[int32]int32, 100)
for i := range shards {
shards[i] = make(map[int32]int32)
}
for i := 0; i < N; i++ {
n := int32(i)
shards[i%100][n] = n
}
runtime.GC()
fmt.Printf("With map shards (%T), GC took %s\n", shards, timeGC())
_ = shards[0][0]
case "5":
// A slice, just for comparison to show that
// merely holding onto millions of int32s is fine
// if they're in a slice.
type t struct {
p, q int32
}
var s []t
for i := 0; i < N; i++ {
n := int32(i)
s = append(s, t{n, n})
}
runtime.GC()
fmt.Printf("With a plain slice (%T), GC took %s\n", s, timeGC())
_ = s[0]
}
}
func timeGC() time.Duration {
start := time.Now()
runtime.GC()
return time.Since(start)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment