DrKey
DrKey

Reputation: 3495

Memory usage: nil interface{} vs struct{}

I'm trying to learn more regarding memory usage.

Doing some tests with interface{} and struct{} slices, I noticed that a slice of struct{} doesn't allocate any memory whereas a slice of interface{} does. It doesn't make so much sense to me, I'm actually expecting the same behavior (ie. both allocate nothing). Anyway I couldn't find any explanation regarding this particular case.

Could someone explain me why this happens?

package main

import (
    "runtime"
    "fmt"
)

func main() {
    // Below is an example of using our PrintMemUsage() function
    // Print our starting memory usage (should be around 0mb)
    fmt.Println("Start")
    PrintMemUsage()
    fmt.Println("")

    structContainer := make([]struct{}, 1000000)
    for i := 0; i<1000000; i++ {
    structContainer[i] = struct{}{}
    }

    fmt.Println("With 1kk struct{}")
    PrintMemUsage()
    fmt.Println("")

    nilContainer := make([]interface{}, 1000000)
    for i := 0; i<1000000; i++ {
    nilContainer[i] = nil
    }

    fmt.Println("With 1kk nil interface{}")
    PrintMemUsage()
    fmt.Println("")
}

// PrintMemUsage outputs the current, total and OS memory being used. As well as the number 
// of garage collection cycles completed.
func PrintMemUsage() {
        var m runtime.MemStats
        runtime.ReadMemStats(&m)
        // For info on each, see: https://golang.org/pkg/runtime/#MemStats
        fmt.Printf("Alloc = %v KiB", bToMb(m.Alloc))
        fmt.Printf("\tTotalAlloc = %v KiB", bToMb(m.TotalAlloc))
        fmt.Printf("\tSys = %v KiB", bToMb(m.Sys))
        fmt.Printf("\tNumGC = %v\n", m.NumGC)
}

func bToMb(b uint64) uint64 {
    return b / 1024
}

Playground link.

Upvotes: 8

Views: 2448

Answers (2)

icza
icza

Reputation: 417712

A variable of type interface{} can hold any value. E.g. it can hold the integer 8, it can hold the string value "hi", it can hold the struct value image.Point{X: 1, Y: 2} and pretty much everything else.

If you allocate a slice having interface{} as its element type, memory have to be allocated so that you can store any values in its elements. When using make() to allocate it, all its elements will get the zero value of the element type (which is nil for the interface{}), but memory still has to be allocated else you couldn't set elements later on.

On the other hand, the empty struct struct{} has no fields, it cannot hold any values (other than struct{}). When you allocate a slice having struct{} as its element type, memory does not need to be allocated because you won't be able to store anything in it that would require memory. So it's a simple and clever optimization not to allocate memory for such a type.

Upvotes: 11

chmike
chmike

Reputation: 22154

This is because an empty struct contains no value.

This is not very useful for arrays or slices. But it is useful for maps. A map without value is like a set. You can insert keys and test if they are present. The absence of value save space as you discovered.

Upvotes: 2

Related Questions