Source file src/internal/runtime/maps/memhash_bench_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build amd64 || arm64
     6  
     7  package maps_test
     8  
     9  import (
    10  	"fmt"
    11  	"testing"
    12  	"unsafe"
    13  
    14  	"internal/runtime/maps"
    15  )
    16  
    17  var sink uintptr
    18  
    19  // BenchmarkHashBakeoff measures the AES and scalar memory hashers at
    20  // various sizes to try to empirically determine when one becomes better than
    21  // the other, for some target uarch. Results are very uarch-dependent!
    22  //
    23  // The datapoints should be compared something like benchstat which uses the
    24  // appropriate statistical tests to knock out outliers.
    25  //
    26  // Latency (i.e., serial pipeline performance) matters for probing, because
    27  // there is a data dependency between the hash and the probe sequence. We can
    28  // measure this by making each iteration of the benchmark depend on the previous
    29  // one. This tends to favor scalar-only hashing more.
    30  //
    31  // Throughput (i.e., how long matters when many independent things are being
    32  // hashed, resulting in better IPC. We measure this by using a seed of 0 for
    33  // each iteration. This tends to favor AES more.
    34  //
    35  // Conservatively, we treat throughput as more important. However, more study
    36  // is needed to determine if prioritizing latency (and thus picking a higher
    37  // cutoff, such as MinLen = 112 on Zen4) results in better macrobenchmarks.
    38  func BenchmarkHashBakeoff(b *testing.B) {
    39  	if !maps.AeshashEnabled() {
    40  		b.Skip("AES hashing not available on this machine")
    41  	}
    42  
    43  	buf := make([]byte, 1024+8)
    44  	for i := range buf {
    45  		buf[i] = byte(i * 63)
    46  	}
    47  	p := unsafe.Pointer(unsafe.SliceData(buf))
    48  
    49  	var sizes = []uintptr{
    50  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
    51  		20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 100, 104, 108, 112, 116,
    52  		120, 124, 128, 192, 256, 512, 1024,
    53  	}
    54  
    55  	for _, s := range sizes {
    56  		b.Run(fmt.Sprintf("scalar/latency/%d", s), func(b *testing.B) {
    57  			var h uintptr
    58  			for b.Loop() {
    59  				h = maps.MemHashFallback(p, h, s)
    60  			}
    61  			sink = h
    62  		})
    63  	}
    64  	for _, s := range sizes {
    65  		b.Run(fmt.Sprintf("scalar/throughput/%d", s), func(b *testing.B) {
    66  			var h uintptr
    67  			for b.Loop() {
    68  				h ^= maps.MemHashFallback(p, 0, s)
    69  			}
    70  			sink = h
    71  		})
    72  	}
    73  	for _, s := range sizes {
    74  		b.Run(fmt.Sprintf("aes/latency/%d", s), func(b *testing.B) {
    75  			var h uintptr
    76  			for b.Loop() {
    77  				h = maps.MemHashAES(p, h, s)
    78  			}
    79  			sink = h
    80  		})
    81  	}
    82  	for _, s := range sizes {
    83  		b.Run(fmt.Sprintf("aes/throughput/%d", s), func(b *testing.B) {
    84  			var h uintptr
    85  			for b.Loop() {
    86  				h ^= maps.MemHashAES(p, 0, s)
    87  			}
    88  			sink = h
    89  		})
    90  	}
    91  }
    92  

View as plain text