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.
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.
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.
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 😄.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With