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