blob: 97b413fe1e4d7b4852333d3356b525b877622612 [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build amd64 || arm64
package maps_test
import (
"fmt"
"testing"
"unsafe"
"internal/runtime/maps"
)
var sink uintptr
// BenchmarkHashBakeoff measures the AES and scalar memory hashers at
// various sizes to try to empirically determine when one becomes better than
// the other, for some target uarch. Results are very uarch-dependent!
//
// The datapoints should be compared something like benchstat which uses the
// appropriate statistical tests to knock out outliers.
//
// Latency (i.e., serial pipeline performance) matters for probing, because
// there is a data dependency between the hash and the probe sequence. We can
// measure this by making each iteration of the benchmark depend on the previous
// one. This tends to favor scalar-only hashing more.
//
// Throughput (i.e., how long matters when many independent things are being
// hashed, resulting in better IPC. We measure this by using a seed of 0 for
// each iteration. This tends to favor AES more.
//
// Conservatively, we treat throughput as more important. However, more study
// is needed to determine if prioritizing latency (and thus picking a higher
// cutoff, such as MinLen = 112 on Zen4) results in better macrobenchmarks.
func BenchmarkHashBakeoff(b *testing.B) {
if !maps.AeshashEnabled() {
b.Skip("AES hashing not available on this machine")
}
buf := make([]byte, 1024+8)
for i := range buf {
buf[i] = byte(i * 63)
}
p := unsafe.Pointer(unsafe.SliceData(buf))
var sizes = []uintptr{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 100, 104, 108, 112, 116,
120, 124, 128, 192, 256, 512, 1024,
}
for _, s := range sizes {
b.Run(fmt.Sprintf("scalar/latency/%d", s), func(b *testing.B) {
var h uintptr
for b.Loop() {
h = maps.MemHashFallback(p, h, s)
}
sink = h
})
}
for _, s := range sizes {
b.Run(fmt.Sprintf("scalar/throughput/%d", s), func(b *testing.B) {
var h uintptr
for b.Loop() {
h ^= maps.MemHashFallback(p, 0, s)
}
sink = h
})
}
for _, s := range sizes {
b.Run(fmt.Sprintf("aes/latency/%d", s), func(b *testing.B) {
var h uintptr
for b.Loop() {
h = maps.MemHashAES(p, h, s)
}
sink = h
})
}
for _, s := range sizes {
b.Run(fmt.Sprintf("aes/throughput/%d", s), func(b *testing.B) {
var h uintptr
for b.Loop() {
h ^= maps.MemHashAES(p, 0, s)
}
sink = h
})
}
}