maps-slices-and-the-go-garbage-collector-explained

Maps, Slices, and the Go Garbage Collector

Home » Programming Language » Golang » Maps, Slices, and the Go Garbage Collector
maps-slices-and-the-go-garbage-collector-explained

In this article, I am going to present you with some examples showing why you should be cautions regarding the operation of the garbage collector. The point of the article is to understand that the way you store pointers has a great impact on the performance of the garbage collector, especially when you are dealing with very large amounts of pointers.

The presented examples will use pointers, slices and maps, which are all native Go data types.

What is Garbage Collection?

Garbage collection is the process of freeing up memory space that is not being used. In other words, the garbage collector sees which objects are out of scope and cannot be referenced anymore, and frees the memory space they consume. This process happens in a concurrent way while a Go program is running and not before or after the execution of the program. According to the documentation of the Go garbage collection:

The GC runs concurrently with mutator threads, is type accurate(also known as precise). allows multiple GC threads to run in parallel. It is a concurrent mark and sweep that uses a write barrier. It is non-generational and non-compacting. Allocation is done using size segregated per P allocation areas to minimize fragmentation while eliminating locks in the common case.

Using a Slice

In this example we will use a slice to store a large amount of structures. Each structure stores two integer values. Follow the below mentioned Go code:

package main

import "runtime"

type data struct{
  i,j int  
}

func main() {
  var N = 40000000
  var str []data

  for i:=0;i<N;i++ {
    value := i
    str = append(str, data{value,value})
  }

  runtime.GC()
  _ = str[0]
}

The last statement (_ = str[0]) is used for preventing the garbage collector from garbage collecting the str variable too early, as it is not referenced or used outside of the for loop. The same technique will be used in the below three Go programs that follow. Apart from this important detail, a for loop is used for putting values into structures that are stored in the slice.

Using a Map with Pointers

In this, we are going to use a map for storing all our pointers as integers. The program contains the following Go code:

package main

import "runtime"

func main() {
  var N = 40000000
  myMap := make(map[int]*int)

  for i:=0;i<N;i++ {
    value := i
    myMap[value] = &value
  }

  runtime.GC()
  _ = myMap[0]
}

The name of the map that stores the integer pointers is myMap. A for loop is used for putting the integer values into map.

Using a Map Without Pointers

In this we are going to use a map that stores plain values without pointers. The Go code is mentioned below:

package main

import "runtime"

func main() {
  var N = 40000000
  myMap := make(map[int]int)

  for i:=0;i<N;i++ {
    value := i
    myMap[value] = value
  }

  runtime.GC()
  _ = myMap[0]
}

As before, a for loop is used for putting the integer values into the map.

Splitting the Map

The implementation of this section will split the map into a map of maps, which is also called sharding. The program of this section contains the following Go code:

package main

import "runtime"

func main() {
  var N = 40000000
  split := make([]map[int]int,200)

  for i := range split{
    split[i] = make(map[int]int)
  }
  for i:=0;i<N;i++ {
    value := i
    split[i%200][value] = value
  }

  runtime.GC()
  _ = split[0][0]
}

This time, we are using two for loops: one for loop for creating the hash of hashes and another one for storing the desired data in the hash of hashes.

Comparing the Performance of the Presented Techniques

As all four programs are using huge data structures, they are consuming large amounts of memory. Program that consumes lots of memory space trigger the Go garbage collector more often. So, in this section we are going to compare the performance of each one of these four implementations using time(1) command.

What will be important in this presented output is not the exact number but the time difference between the four different approaches. Here we go:


$ time go run 1_sliceGC.go

real    0m1.511s
user    0m0.000s
sys     0m0.015s


$ time go run 2_mapStar.go

real    0m10.395s
user    0m0.000s
sys     0m0.015s


$ time go run 3_mapNoStar.go

real    0m8.227s
user    0m0.000s
sys     0m0.015s


$ time go run 4_mapSplit.go

real    0m8.028s
user    0m0.000s
sys     0m0.015s

So, it turns out that maps slow down the Go garbage collector whereas slices collaborate much better with it. It should be noted here that this is not a problem with maps but a result of the way the Go garbage collector works. However, unless you are dealing with maps that store huge amounts of data, this problem will not become evident in your programs.

Conclusion

In this article we covered maps, slices with Go garbage collector and also we checked the performance of maps and slices with huge amount of data in which slices worked fast in comparison to map.

your comments are appreciated and if you wants to see your articles on this platform then please shoot a mail at this address kusingh@programmingeeksclub.com

Thanks for reading 🙂

Join Our Newsletter!

Join our newsletter to get our latest ebook "Ultimate JavaScript Cheat-Sheet", and Tips, Articles..

We don’t spam! Read our privacy policy for more info.

Join Our Newsletter!

Join our newsletter to get our latest ebook "Ultimate JavaScript Cheat-Sheet", and Tips, Articles..

We don’t spam! Read our privacy policy for more info.

Leave a Comment

Your email address will not be published. Required fields are marked *


The reCAPTCHA verification period has expired. Please reload the page.