| // 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 | |
| }) | |
| } | |
| } |