mirror of
https://github.com/golang/go
synced 2024-11-21 23:54:40 -07:00
runtime: more thorough map benchmarks
Based on the benchmarks in github.com/cockroachlabs/swiss. For #54766. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest-swissmap Change-Id: I9ad925d3272c671e21ec04eb2da5ebd8f0fc6a28 Reviewed-on: https://go-review.googlesource.com/c/go/+/596295 Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Keith Randall <khr@google.com> Auto-Submit: Michael Pratt <mpratt@google.com>
This commit is contained in:
parent
a8e2ecc8b1
commit
488e2d18d9
@ -5,11 +5,15 @@
|
||||
package runtime_test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const size = 10
|
||||
@ -206,17 +210,6 @@ func benchmarkMapStringKeysEight(b *testing.B, keySize int) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIntMap(b *testing.B) {
|
||||
m := make(map[int]bool)
|
||||
for i := 0; i < 8; i++ {
|
||||
m[i] = true
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = m[7]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapFirst(b *testing.B) {
|
||||
for n := 1; n <= 16; n++ {
|
||||
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
|
||||
@ -333,27 +326,6 @@ func BenchmarkNewSmallMap(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapIter(b *testing.B) {
|
||||
m := make(map[int]bool)
|
||||
for i := 0; i < 8; i++ {
|
||||
m[i] = true
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range m {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapIterEmpty(b *testing.B) {
|
||||
m := make(map[int]bool)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range m {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSameLengthMap(b *testing.B) {
|
||||
// long strings, same length, differ in first few
|
||||
// and last few bytes.
|
||||
@ -368,28 +340,6 @@ func BenchmarkSameLengthMap(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
type BigKey [3]int64
|
||||
|
||||
func BenchmarkBigKeyMap(b *testing.B) {
|
||||
m := make(map[BigKey]bool)
|
||||
k := BigKey{3, 4, 5}
|
||||
m[k] = true
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = m[k]
|
||||
}
|
||||
}
|
||||
|
||||
type BigVal [3]int64
|
||||
|
||||
func BenchmarkBigValMap(b *testing.B) {
|
||||
m := make(map[BigKey]BigVal)
|
||||
k := BigKey{3, 4, 5}
|
||||
m[k] = BigVal{6, 7, 8}
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = m[k]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSmallKeyMap(b *testing.B) {
|
||||
m := make(map[int16]bool)
|
||||
m[5] = true
|
||||
@ -538,3 +488,586 @@ func BenchmarkNewEmptyMapHintGreaterThan8(b *testing.B) {
|
||||
_ = make(map[int]int, hintGreaterThan8)
|
||||
}
|
||||
}
|
||||
|
||||
func benchSizes(f func(b *testing.B, n int)) func(*testing.B) {
|
||||
var cases = []int{
|
||||
0,
|
||||
6,
|
||||
12,
|
||||
18,
|
||||
24,
|
||||
30,
|
||||
64,
|
||||
128,
|
||||
256,
|
||||
512,
|
||||
1024,
|
||||
2048,
|
||||
4096,
|
||||
8192,
|
||||
1 << 16,
|
||||
1 << 18,
|
||||
1 << 20,
|
||||
1 << 22,
|
||||
}
|
||||
|
||||
return func(b *testing.B) {
|
||||
for _, n := range cases {
|
||||
b.Run("len="+strconv.Itoa(n), func(b *testing.B) {
|
||||
f(b, n)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A 16 byte type.
|
||||
type smallType [16]byte
|
||||
|
||||
// A 512 byte type.
|
||||
type mediumType [1 << 9]byte
|
||||
|
||||
// A 4KiB type.
|
||||
type bigType [1 << 12]byte
|
||||
|
||||
type mapBenchmarkKeyType interface {
|
||||
int32 | int64 | string | smallType | mediumType | bigType | *int32
|
||||
}
|
||||
|
||||
type mapBenchmarkElemType interface {
|
||||
mapBenchmarkKeyType | []int32
|
||||
}
|
||||
|
||||
func genIntValues[T int | int32 | int64](start, end int) []T {
|
||||
vals := make([]T, 0, end-start)
|
||||
for i := start; i < end; i++ {
|
||||
vals = append(vals, T(i))
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func genStringValues(start, end int) []string {
|
||||
vals := make([]string, 0, end-start)
|
||||
for i := start; i < end; i++ {
|
||||
vals = append(vals, strconv.Itoa(i))
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func genSmallValues(start, end int) []smallType {
|
||||
vals := make([]smallType, 0, end-start)
|
||||
for i := start; i < end; i++ {
|
||||
var v smallType
|
||||
binary.NativeEndian.PutUint64(v[:], uint64(i))
|
||||
vals = append(vals, v)
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func genMediumValues(start, end int) []mediumType {
|
||||
vals := make([]mediumType, 0, end-start)
|
||||
for i := start; i < end; i++ {
|
||||
var v mediumType
|
||||
binary.NativeEndian.PutUint64(v[:], uint64(i))
|
||||
vals = append(vals, v)
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func genBigValues(start, end int) []bigType {
|
||||
vals := make([]bigType, 0, end-start)
|
||||
for i := start; i < end; i++ {
|
||||
var v bigType
|
||||
binary.NativeEndian.PutUint64(v[:], uint64(i))
|
||||
vals = append(vals, v)
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func genPtrValues[T any](start, end int) []*T {
|
||||
// Start and end don't mean much. Each pointer by definition has a
|
||||
// unique identity.
|
||||
vals := make([]*T, 0, end-start)
|
||||
for i := start; i < end; i++ {
|
||||
v := new(T)
|
||||
vals = append(vals, v)
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func genIntSliceValues[T int | int32 | int64](start, end int) [][]T {
|
||||
vals := make([][]T, 0, end-start)
|
||||
for i := start; i < end; i++ {
|
||||
vals = append(vals, []T{T(i)})
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func genValues[T mapBenchmarkElemType](start, end int) []T {
|
||||
var t T
|
||||
switch any(t).(type) {
|
||||
case int32:
|
||||
return any(genIntValues[int32](start, end)).([]T)
|
||||
case int64:
|
||||
return any(genIntValues[int64](start, end)).([]T)
|
||||
case string:
|
||||
return any(genStringValues(start, end)).([]T)
|
||||
case smallType:
|
||||
return any(genSmallValues(start, end)).([]T)
|
||||
case mediumType:
|
||||
return any(genMediumValues(start, end)).([]T)
|
||||
case bigType:
|
||||
return any(genBigValues(start, end)).([]T)
|
||||
case *int32:
|
||||
return any(genPtrValues[int32](start, end)).([]T)
|
||||
case []int32:
|
||||
return any(genIntSliceValues[int32](start, end)).([]T)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid inlining to force a heap allocation.
|
||||
//
|
||||
//go:noinline
|
||||
func newSink[T mapBenchmarkElemType]() *T {
|
||||
return new(T)
|
||||
}
|
||||
|
||||
// Return a new maps filled with keys and elems. Both slices must be the same length.
|
||||
func fillMap[K mapBenchmarkKeyType, E mapBenchmarkElemType](keys []K, elems []E) map[K]E {
|
||||
m := make(map[K]E, len(keys))
|
||||
for i := range keys {
|
||||
m[keys[i]] = elems[i]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func iterCount(b *testing.B, n int) int {
|
||||
// Divide b.N by n so that the ns/op reports time per element,
|
||||
// not time per full map iteration. This makes benchmarks of
|
||||
// different map sizes more comparable.
|
||||
//
|
||||
// If size is zero we still need to do iterations.
|
||||
if n == 0 {
|
||||
return b.N
|
||||
}
|
||||
return b.N / n
|
||||
}
|
||||
|
||||
func checkAllocSize[K, E any](b *testing.B, n int) {
|
||||
var k K
|
||||
size := uint64(n) * uint64(unsafe.Sizeof(k))
|
||||
var e E
|
||||
size += uint64(n) * uint64(unsafe.Sizeof(e))
|
||||
|
||||
if size >= 1<<30 {
|
||||
b.Skipf("Total key+elem size %d exceeds 1GiB", size)
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapIter[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
m := fillMap(k, e)
|
||||
iterations := iterCount(b, n)
|
||||
sinkK := newSink[K]()
|
||||
sinkE := newSink[E]()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < iterations; i++ {
|
||||
for k, e := range m {
|
||||
*sinkK = k
|
||||
*sinkE = e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapIter(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapIter[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapIter[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapIter[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapIter[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapIter[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapIter[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapIter[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapIter[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapIter[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapIter[int32, *int32]))
|
||||
}
|
||||
|
||||
func benchmarkMapAccessHit[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't access empty map")
|
||||
}
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
m := fillMap(k, e)
|
||||
sink := newSink[E]()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
*sink = m[k[i%n]]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapAccessHit(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAccessHit[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAccessHit[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAccessHit[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAccessHit[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAccessHit[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAccessHit[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAccessHit[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAccessHit[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAccessHit[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAccessHit[int32, *int32]))
|
||||
}
|
||||
|
||||
var sinkOK bool
|
||||
|
||||
func benchmarkMapAccessMiss[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
m := fillMap(k, e)
|
||||
if n == 0 { // Create a lookup values for empty maps.
|
||||
n = 1
|
||||
}
|
||||
w := genValues[K](n, 2*n)
|
||||
b.ResetTimer()
|
||||
|
||||
var ok bool
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, ok = m[w[i%n]]
|
||||
}
|
||||
|
||||
sinkOK = ok
|
||||
}
|
||||
|
||||
func BenchmarkMapAccessMiss(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAccessMiss[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAccessMiss[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAccessMiss[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAccessMiss[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAccessMiss[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAccessMiss[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAccessMiss[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAccessMiss[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAccessMiss[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAccessMiss[int32, *int32]))
|
||||
}
|
||||
|
||||
// Assign to a key that already exists.
|
||||
func benchmarkMapAssignExists[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't assign to existing keys in empty map")
|
||||
}
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
m := fillMap(k, e)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
m[k[i%n]] = e[i%n]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapAssignExists(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignExists[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignExists[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignExists[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignExists[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignExists[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignExists[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignExists[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignExists[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignExists[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignExists[int32, *int32]))
|
||||
}
|
||||
|
||||
// Fill a map of size n with no hint. Time is per-key. A new map is created
|
||||
// every n assignments.
|
||||
//
|
||||
// TODO(prattmic): Results don't make much sense if b.N < n.
|
||||
// TODO(prattmic): Measure distribution of assign time to reveal the grow
|
||||
// latency.
|
||||
func benchmarkMapAssignFillNoHint[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't create empty map via assignment")
|
||||
}
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
b.ResetTimer()
|
||||
|
||||
var m map[K]E
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i%n == 0 {
|
||||
m = make(map[K]E)
|
||||
}
|
||||
m[k[i%n]] = e[i%n]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapAssignFillNoHint(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignFillNoHint[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignFillNoHint[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignFillNoHint[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignFillNoHint[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignFillNoHint[int32, *int32]))
|
||||
}
|
||||
|
||||
// Identical to benchmarkMapAssignFillNoHint, but additionally measures the
|
||||
// latency of each mapassign to report tail latency due to map grow.
|
||||
func benchmarkMapAssignGrowLatency[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't create empty map via assignment")
|
||||
}
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
|
||||
// Store the run time of each mapassign. Keeping the full data rather
|
||||
// than a histogram provides higher precision. b.N tends to be <10M, so
|
||||
// the memory requirement isn't too bad.
|
||||
sample := make([]int64, b.N)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
var m map[K]E
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i%n == 0 {
|
||||
m = make(map[K]E)
|
||||
}
|
||||
start := runtime.Nanotime()
|
||||
m[k[i%n]] = e[i%n]
|
||||
end := runtime.Nanotime()
|
||||
sample[i] = end - start
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
|
||||
slices.Sort(sample)
|
||||
// TODO(prattmic): Grow is so rare that even p99.99 often doesn't
|
||||
// display a grow case. Switch to a more direct measure of grow cases
|
||||
// only?
|
||||
b.ReportMetric(float64(sample[int(float64(len(sample))*0.5)]), "p50-ns/op")
|
||||
b.ReportMetric(float64(sample[int(float64(len(sample))*0.99)]), "p99-ns/op")
|
||||
b.ReportMetric(float64(sample[int(float64(len(sample))*0.999)]), "p99.9-ns/op")
|
||||
b.ReportMetric(float64(sample[int(float64(len(sample))*0.9999)]), "p99.99-ns/op")
|
||||
b.ReportMetric(float64(sample[len(sample)-1]), "p100-ns/op")
|
||||
}
|
||||
|
||||
func BenchmarkMapAssignGrowLatency(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignGrowLatency[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignGrowLatency[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignGrowLatency[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignGrowLatency[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignGrowLatency[int32, *int32]))
|
||||
}
|
||||
|
||||
// Fill a map of size n with size hint. Time is per-key. A new map is created
|
||||
// every n assignments.
|
||||
//
|
||||
// TODO(prattmic): Results don't make much sense if b.N < n.
|
||||
func benchmarkMapAssignFillHint[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't create empty map via assignment")
|
||||
}
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
b.ResetTimer()
|
||||
|
||||
var m map[K]E
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i%n == 0 {
|
||||
m = make(map[K]E, n)
|
||||
}
|
||||
m[k[i%n]] = e[i%n]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapAssignFillHint(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignFillHint[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignFillHint[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignFillHint[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignFillHint[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignFillHint[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignFillHint[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignFillHint[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignFillHint[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignFillHint[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignFillHint[int32, *int32]))
|
||||
}
|
||||
|
||||
// Fill a map of size n, reusing the same map. Time is per-key. The map is
|
||||
// cleared every n assignments.
|
||||
//
|
||||
// TODO(prattmic): Results don't make much sense if b.N < n.
|
||||
func benchmarkMapAssignFillClear[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't create empty map via assignment")
|
||||
}
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
m := fillMap(k, e)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i%n == 0 {
|
||||
clear(m)
|
||||
}
|
||||
m[k[i%n]] = e[i%n]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapAssignFillClear(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignFillClear[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignFillClear[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignFillClear[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignFillClear[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignFillClear[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignFillClear[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignFillClear[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignFillClear[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignFillClear[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignFillClear[int32, *int32]))
|
||||
}
|
||||
|
||||
// Modify values using +=.
|
||||
func benchmarkMapAssignAddition[K mapBenchmarkKeyType, E int32 | int64 | string](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't modify empty map via assignment")
|
||||
}
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
m := fillMap(k, e)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
m[k[i%n]] += e[i%n]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapAssignAddition(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignAddition[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignAddition[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignAddition[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignAddition[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignAddition[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignAddition[bigType, int32]))
|
||||
}
|
||||
|
||||
// Modify values append.
|
||||
func benchmarkMapAssignAppend[K mapBenchmarkKeyType](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't modify empty map via append")
|
||||
}
|
||||
checkAllocSize[K, []int32](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[[]int32](0, n)
|
||||
m := fillMap(k, e)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
m[k[i%n]] = append(m[k[i%n]], e[i%n][0])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapAssignAppend(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=[]int32", benchSizes(benchmarkMapAssignAppend[int32]))
|
||||
b.Run("Key=int64/Elem=[]int32", benchSizes(benchmarkMapAssignAppend[int64]))
|
||||
b.Run("Key=string/Elem=[]int32", benchSizes(benchmarkMapAssignAppend[string]))
|
||||
}
|
||||
|
||||
func benchmarkMapDelete[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't delete from empty map")
|
||||
}
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
m := fillMap(k, e)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if len(m) == 0 {
|
||||
b.StopTimer()
|
||||
for j := range k {
|
||||
m[k[j]] = e[j]
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
delete(m, k[i%n])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapDelete(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapDelete[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapDelete[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapDelete[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapDelete[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapDelete[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapDelete[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapDelete[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapDelete[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapDelete[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapDelete[int32, *int32]))
|
||||
}
|
||||
|
||||
// Use iterator to pop an element. We want this to be fast, see
|
||||
// https://go.dev/issue/8412.
|
||||
func benchmarkMapPop[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
|
||||
if n == 0 {
|
||||
b.Skip("can't delete from empty map")
|
||||
}
|
||||
checkAllocSize[K, E](b, n)
|
||||
k := genValues[K](0, n)
|
||||
e := genValues[E](0, n)
|
||||
m := fillMap(k, e)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if len(m) == 0 {
|
||||
// We'd like to StopTimer while refilling the map, but
|
||||
// it is way too expensive and thus makes the benchmark
|
||||
// take a long time. See https://go.dev/issue/20875.
|
||||
for j := range k {
|
||||
m[k[j]] = e[j]
|
||||
}
|
||||
}
|
||||
for key := range m {
|
||||
delete(m, key)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapPop(b *testing.B) {
|
||||
b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapPop[int32, int32]))
|
||||
b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapPop[int64, int64]))
|
||||
b.Run("Key=string/Elem=string", benchSizes(benchmarkMapPop[string, string]))
|
||||
b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapPop[smallType, int32]))
|
||||
b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapPop[mediumType, int32]))
|
||||
b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapPop[bigType, int32]))
|
||||
b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapPop[bigType, bigType]))
|
||||
b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapPop[int32, bigType]))
|
||||
b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapPop[*int32, int32]))
|
||||
b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapPop[int32, *int32]))
|
||||
}
|
||||
|
@ -639,27 +639,6 @@ func TestIgnoreBogusMapHint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapPop(b *testing.B, n int) {
|
||||
m := map[int]int{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < n; j++ {
|
||||
m[j] = j
|
||||
}
|
||||
for j := 0; j < n; j++ {
|
||||
// Use iterator to pop an element.
|
||||
// We want this to be fast, see issue 8412.
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapPop100(b *testing.B) { benchmarkMapPop(b, 100) }
|
||||
func BenchmarkMapPop1000(b *testing.B) { benchmarkMapPop(b, 1000) }
|
||||
func BenchmarkMapPop10000(b *testing.B) { benchmarkMapPop(b, 10000) }
|
||||
|
||||
var testNonEscapingMapVariable int = 8
|
||||
|
||||
func TestNonEscapingMap(t *testing.T) {
|
||||
@ -698,198 +677,6 @@ func TestNonEscapingMap(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func benchmarkMapAssignInt32(b *testing.B, n int) {
|
||||
a := make(map[int32]int)
|
||||
for i := 0; i < b.N; i++ {
|
||||
a[int32(i&(n-1))] = i
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapOperatorAssignInt32(b *testing.B, n int) {
|
||||
a := make(map[int32]int)
|
||||
for i := 0; i < b.N; i++ {
|
||||
a[int32(i&(n-1))] += i
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapAppendAssignInt32(b *testing.B, n int) {
|
||||
a := make(map[int32][]int)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
key := int32(i & (n - 1))
|
||||
a[key] = append(a[key], i)
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapDeleteInt32(b *testing.B, n int) {
|
||||
a := make(map[int32]int, n)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if len(a) == 0 {
|
||||
b.StopTimer()
|
||||
for j := i; j < i+n; j++ {
|
||||
a[int32(j)] = j
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
delete(a, int32(i))
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapAssignInt64(b *testing.B, n int) {
|
||||
a := make(map[int64]int)
|
||||
for i := 0; i < b.N; i++ {
|
||||
a[int64(i&(n-1))] = i
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapOperatorAssignInt64(b *testing.B, n int) {
|
||||
a := make(map[int64]int)
|
||||
for i := 0; i < b.N; i++ {
|
||||
a[int64(i&(n-1))] += i
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapAppendAssignInt64(b *testing.B, n int) {
|
||||
a := make(map[int64][]int)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
key := int64(i & (n - 1))
|
||||
a[key] = append(a[key], i)
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapDeleteInt64(b *testing.B, n int) {
|
||||
a := make(map[int64]int, n)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if len(a) == 0 {
|
||||
b.StopTimer()
|
||||
for j := i; j < i+n; j++ {
|
||||
a[int64(j)] = j
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
delete(a, int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapAssignStr(b *testing.B, n int) {
|
||||
k := make([]string, n)
|
||||
for i := 0; i < len(k); i++ {
|
||||
k[i] = strconv.Itoa(i)
|
||||
}
|
||||
b.ResetTimer()
|
||||
a := make(map[string]int)
|
||||
for i := 0; i < b.N; i++ {
|
||||
a[k[i&(n-1)]] = i
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapOperatorAssignStr(b *testing.B, n int) {
|
||||
k := make([]string, n)
|
||||
for i := 0; i < len(k); i++ {
|
||||
k[i] = strconv.Itoa(i)
|
||||
}
|
||||
b.ResetTimer()
|
||||
a := make(map[string]string)
|
||||
for i := 0; i < b.N; i++ {
|
||||
key := k[i&(n-1)]
|
||||
a[key] += key
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapAppendAssignStr(b *testing.B, n int) {
|
||||
k := make([]string, n)
|
||||
for i := 0; i < len(k); i++ {
|
||||
k[i] = strconv.Itoa(i)
|
||||
}
|
||||
a := make(map[string][]string)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
key := k[i&(n-1)]
|
||||
a[key] = append(a[key], key)
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapDeleteStr(b *testing.B, n int) {
|
||||
i2s := make([]string, n)
|
||||
for i := 0; i < n; i++ {
|
||||
i2s[i] = strconv.Itoa(i)
|
||||
}
|
||||
a := make(map[string]int, n)
|
||||
b.ResetTimer()
|
||||
k := 0
|
||||
for i := 0; i < b.N; i++ {
|
||||
if len(a) == 0 {
|
||||
b.StopTimer()
|
||||
for j := 0; j < n; j++ {
|
||||
a[i2s[j]] = j
|
||||
}
|
||||
k = i
|
||||
b.StartTimer()
|
||||
}
|
||||
delete(a, i2s[i-k])
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMapDeletePointer(b *testing.B, n int) {
|
||||
i2p := make([]*int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
i2p[i] = new(int)
|
||||
}
|
||||
a := make(map[*int]int, n)
|
||||
b.ResetTimer()
|
||||
k := 0
|
||||
for i := 0; i < b.N; i++ {
|
||||
if len(a) == 0 {
|
||||
b.StopTimer()
|
||||
for j := 0; j < n; j++ {
|
||||
a[i2p[j]] = j
|
||||
}
|
||||
k = i
|
||||
b.StartTimer()
|
||||
}
|
||||
delete(a, i2p[i-k])
|
||||
}
|
||||
}
|
||||
|
||||
func runWith(f func(*testing.B, int), v ...int) func(*testing.B) {
|
||||
return func(b *testing.B) {
|
||||
for _, n := range v {
|
||||
b.Run(strconv.Itoa(n), func(b *testing.B) { f(b, n) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapAssign(b *testing.B) {
|
||||
b.Run("Int32", runWith(benchmarkMapAssignInt32, 1<<8, 1<<16))
|
||||
b.Run("Int64", runWith(benchmarkMapAssignInt64, 1<<8, 1<<16))
|
||||
b.Run("Str", runWith(benchmarkMapAssignStr, 1<<8, 1<<16))
|
||||
}
|
||||
|
||||
func BenchmarkMapOperatorAssign(b *testing.B) {
|
||||
b.Run("Int32", runWith(benchmarkMapOperatorAssignInt32, 1<<8, 1<<16))
|
||||
b.Run("Int64", runWith(benchmarkMapOperatorAssignInt64, 1<<8, 1<<16))
|
||||
b.Run("Str", runWith(benchmarkMapOperatorAssignStr, 1<<8, 1<<16))
|
||||
}
|
||||
|
||||
func BenchmarkMapAppendAssign(b *testing.B) {
|
||||
b.Run("Int32", runWith(benchmarkMapAppendAssignInt32, 1<<8, 1<<16))
|
||||
b.Run("Int64", runWith(benchmarkMapAppendAssignInt64, 1<<8, 1<<16))
|
||||
b.Run("Str", runWith(benchmarkMapAppendAssignStr, 1<<8, 1<<16))
|
||||
}
|
||||
|
||||
func BenchmarkMapDelete(b *testing.B) {
|
||||
b.Run("Int32", runWith(benchmarkMapDeleteInt32, 100, 1000, 10000))
|
||||
b.Run("Int64", runWith(benchmarkMapDeleteInt64, 100, 1000, 10000))
|
||||
b.Run("Str", runWith(benchmarkMapDeleteStr, 100, 1000, 10000))
|
||||
b.Run("Pointer", runWith(benchmarkMapDeletePointer, 100, 1000, 10000))
|
||||
}
|
||||
|
||||
func TestDeferDeleteSlow(t *testing.T) {
|
||||
ks := []complex128{0, 1, 2, 3}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user