With swissmaps in 1.24, a map[int64]struct{}
requires 16 bytes of space per slot, rather than the expected 8 bytes.
This is an unfortunate side effect of the way the storage is defined internally https://cs.opensource.google/go/go/+/master:src/cmd/compile/internal/reflectdata/map_swiss.go;l=30
// type group struct {
// ctrl uint64
// slots [abi.SwissMapGroupSlots]struct {
// key keyType
// elem elemType
// }
// }
elemType
is struct{}
. The struct size rules in the compiler say that if struct ends in a zero-size type, that field is given 1 byte of space (in case someone creates a pointer to the last field, we don't want that to point past the end of allocation).
Then, keyType
needs 8-byte alignment, so the last field actually ends up using a full 8-bytes.
This is the most extreme case of KVKVKVKV wasting space due to alignment requirements. We could probably fix this specific case by deconstructing the array+struct in this internal definition, then we'd only need the extra 8 bytes for the last slot.
Note that #70835 considers changing the layout entirely (putting all keys together), which would also fix this.
Comment From: thediveo
Is this basically "as bad" as the also common map[X]bool
, where the bool actually isn't used but just because is easier to type/read than struct{}
?
Comment From: prattmic
Yes, both now allocate exactly the same amount of memory.
Comment From: zigo101
Will
struct {
elem elemType
key keyType
}
fix this?
Comment From: valyala
This may be the reason of performance drop for map[uint64]struct{}
maps with millions of items when migrating from Go1.23 to Go1.24 - https://x.com/valyala/status/1879988053076504761
Comment From: jub0bs
Relevant discussion on Gophers Slack: https://gophers.slack.com/archives/C0VP8EF3R/p1737383438473369
Comment From: jub0bs
@prattmic Any thoughts on @zigo101's comment? Flipping the key
and elem
fields in the declaration of the slot struct type sounds promising and simple enough, but perhaps I'm failing to anticipate the ramifications of such a change.
Comment From: bboreham
I looked into "Flipping the key and elem fields in the declaration of the slot struct type".
The code maintains ElemOff
, the offset of the element within a slot: https://github.com/golang/go/blob/084c0f849467d5e45b7242cda3dd957352e86b8f/src/internal/runtime/maps/group.go#L290-L291
but has no matching KeyOff
, i.e. it assumes the key is at offset 0. https://github.com/golang/go/blob/084c0f849467d5e45b7242cda3dd957352e86b8f/src/internal/runtime/maps/group.go#L283-L284
Because the key is used more often than the element, an extra addition to get its address might be expected to slow the map a little.
ElemOff
is used in about 8 places, plus there are some that hard-code the value, e.g. https://github.com/golang/go/blob/084c0f849467d5e45b7242cda3dd957352e86b8f/src/internal/runtime/maps/runtime_fast64.go#L40
So not completely simple to flip them around.