Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Computing the memory footprint (or byte length) of a map

Tags:

dictionary

go

I want to limit a map to be maximum X bytes. It seems there is no straightforward way of computing the byte length of a map though.

"encoding/binary" package has a nice Size function, but it only works for slices or "fixed values", not for maps.

I could try to get all key/value pairs from the map, infer their type (if it's a map[string]interface{}) and compute the length - but that would be both cumbersome and probably incorrect (because that would exclude the "internal" Go cost of the map itself - managing pointers to elements etc).

Any suggested way of doing this? Preferably a code example.

like image 431
orcaman Avatar asked Aug 06 '15 05:08

orcaman


People also ask

How much memory does a map take?

A map with 150 million nodes soaked up ~ 15GB, which implies the 8 byte L, 8 byte R, 8 byte int key, and 8 byte datum, totaling 32 bytes, soaked up about 2/3rds of the map's memory for internal nodes, leaving 1/3rd for leaves. Personally, I found this to be surprisingly poor memory efficiency, but it is what it is.

How much memory does a Golang map use?

This is detailed in this answer: How to get variable memory size of variable in golang? So the answer is on my 64-bit architecture: 48 bytes. A map of type map[string]int with an initial capacity of 100 now requires 4176 bytes (on 64-bit arch). The default initial capacity is around 7 if not specified explicitly.

Are Maps slow Golang?

Maps of type map[int]interface{} are slower because they suffer performance degradation when GC scans the buckets that can hold pointers. Maps of type map[int]struct{} use less memory because they, in fact, use less memory 😄.


1 Answers

This is the definition for a map header:

// A header for a Go map.
type hmap struct {
    // Note: the format of the Hmap is encoded in ../../cmd/gc/reflect.c and
    // ../reflect/type.go.  Don't change this structure without also changing that code!
    count int // # live cells == size of map.  Must be first (used by len() builtin)
    flags uint32
    hash0 uint32 // hash seed
    B     uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)

    buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
}

Calculating its size is pretty straightforward (unsafe.Sizeof).

This is the definition for each individual bucket the map points to:

// A bucket for a Go map.
type bmap struct {
    tophash [bucketCnt]uint8
    // Followed by bucketCnt keys and then bucketCnt values.
    // NOTE: packing all the keys together and then all the values together makes the
    // code a bit more complicated than alternating key/value/key/value/... but it allows
    // us to eliminate padding which would be needed for, e.g., map[int64]int8.
    // Followed by an overflow pointer.
}

bucketCnt is a constant defined as:

bucketCnt     = 1 << bucketCntBits // equals decimal 8
bucketCntBits = 3

The final calculation would be:

unsafe.Sizeof(hmap) + (len(theMap) * 8) + (len(theMap) * 8 * unsafe.Sizeof(x)) + (len(theMap) * 8 * unsafe.Sizeof(y))

Where theMap is your map value, x is a value of the map's key type and y a value of the map's value type.

You'll have to share the hmap structure with your package via assembly, analogously to thunk.s in the runtime.

like image 68
thwd Avatar answered Sep 27 '22 16:09

thwd