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.