Swiss Maps and maphash
Swiss Maps and maphash
Overview
Go’s map implementation has evolved. Recent versions moved toward Swiss Table-style layouts (open addressing with group metadata) for better CPU cache behavior versus older bucket-chain designs. Exact rollout is versioned — treat this as a performance model, not a guarantee of on-disk layout.
Complement: Slice & map internals.
Diagram: Map lookup path
key ──hash──► probe groups / buckets ──► slot
high load ──► grow / evacuate
maphash.Seed ──► custom tables / sharding
What Changed Conceptually
Legacy (simplified): buckets -> overflow lists
Swiss-style: groups of slots + control bytes (SIMD-friendly probes)
Implications:
- Better average lookup locality
- Different resize/evacuation behavior
- Iteration order still randomized — never depend on it
- Concurrent map misuse still fatal
Practical Advice (Unchanged)
m := make(map[string]T, approxSize) // still helps- Pre-size when cardinality known
- Prefer
string/ integer keys over allocations as keys - Do not store huge structs as values if you update fields often (consider
map[K]*T)
maphash
Package hash/maphash provides high-quality, process-randomized hashing suitable for your own hash tables and sharding:
import "hash/maphash"
var seed maphash.Seed // zero value ok; or maphash.MakeSeed()
func hashString(s string) uint64 {
var h maphash.Hash
h.SetSeed(seed)
h.WriteString(s)
return h.Sum64()
}Properties:
- Seed differs per process → harder hash-flooding for custom maps
- Not a crypto hash
- Fast for strings/bytes
Use for:
- Sharding keys across workers
- Custom open-address tables
- Consistent bucketing within one process (not cross-process stable unless you fix a seed carefully and accept risks)
Map Iteration and Deletes
for k := range m {
if shouldDrop(k) {
delete(m, k) // allowed in range
}
}Experiment
go mod init example
go test -bench=. -benchmempackage mapspeed_test
import (
"hash/maphash"
"testing"
)
func BenchmarkMapString(b *testing.B) {
m := make(map[string]int, b.N)
for i := 0; i < b.N; i++ {
m[string(rune('a'+i%26))+string(rune(i))] = i
}
}
func BenchmarkMaphash(b *testing.B) {
var seed maphash.Seed
var h maphash.Hash
s := "authorization"
b.ResetTimer()
for i := 0; i < b.N; i++ {
h.SetSeed(seed)
h.WriteString(s)
_ = h.Sum64()
h.Reset()
}
}What to notice: Built-in maps are heavily optimized; custom maps need maphash + careful probing to approach them.
Try next: Read Go release notes for your version mentioning map changes; re-run production pprof after upgrades.