Repository: go
Issue: runtime: map cold cache improvements
Labels: Performance,NeedsInvestigation,compiler/runtime

There is room for improvement when some or all of a map is not in cache. The hierarchical design we use to enable incremental growth leaves us with several different allocations that can experience cache misses:

1. The Map itself (on [m.Used](https://cs.opensource.google/go/go/+/master:src/internal/runtime/maps/runtime_fast32_swiss.go;l=24;drc=dce30a19200d06b778fa90c96231b3cf71fe72d8))
1. The directory slice (on [m.directoryAt](https://cs.opensource.google/go/go/+/master:src/internal/runtime/maps/runtime_fast32_swiss.go;l=56;drc=dce30a19200d06b778fa90c96231b3cf71fe72d8))
1. The table (on [t.groups.lengthMask](https://cs.opensource.google/go/go/+/master:src/internal/runtime/maps/runtime_fast32_swiss.go;l=59;drc=dce30a19200d06b778fa90c96231b3cf71fe72d8))
1. The control word (on [matchH2](https://cs.opensource.google/go/go/+/master:src/internal/runtime/maps/runtime_fast32_swiss.go;l=63;drc=dce30a19200d06b778fa90c96231b3cf71fe72d8))
1. The slot key (on [key comparison](https://cs.opensource.google/go/go/+/master:src/internal/runtime/maps/runtime_fast32_swiss.go;l=69;drc=dce30a19200d06b778fa90c96231b3cf71fe72d8))
1. The slot value (on access in the caller)

(4), (5), and (6) are in the same object, so they may share a cache line depending on how far apart they are.

Some changes that may help (or may make things worse!):

* Grouping control words together ([prototype](https://go.dev/cl/635756)).
  * Good for locality of control words. Probably most useful for lookup of keys that aren't in the map (as the key has worse locality with the control word).
  * Also good for optimistic prefetch of the first control word, something that [Abseil does](https://github.com/abseil/abseil-cpp/blob/7316f5616bad0a794b2a75901cc20b0099718085/absl/container/internal/raw_hash_set.h#L4052).
* Change the directory from `[]*table` to `[]table` ([prototype](https://go.dev/cl/635757)).
  * Effectively merges cases (2) and (3).
* Store group data directly in the table. The table becomes variable size, with a `table8` containing 1 group, `table16` containing 2 groups, and so on up to `table1024`.
  * Effectively merges cases (3) and (4).
  * Allows optimistic prefetch of the control word / first key before even touching the table as they are at fixed offsets.
  * We'd likely want to store the size in the directory as well to enable above.
  * This is unfortunately mutually exclusive with the previous option, as the table is variable-sized.

There is also the question of whether to store the key and value together (KVKVKV...), as we do today, or to group the keys together (KKKVVV...), as we do in the old maps. Both cases have pros and cons:

K/V together:
* Map hit
  * Cache miss on control word
  * Cache miss on key comparison
  * No cache miss on value use in parent
* Map miss
  * Cache miss on control word
  * Cache miss on key comparison in false positive matches (~1%)
  * No value

All keys together:
* Map hit
  * Cache miss on control word
  * No cache miss on key comparison
  * Cache miss on value use in parent (if used)
* Map miss
  * Cache miss on control word
  * No cache miss on key comparison in false positive matches (~1%)
  * No value