Source file
src/runtime/map_benchmark_test.go
1
2
3
4
5 package runtime_test
6
7 import (
8 "encoding/binary"
9 "flag"
10 "fmt"
11 "math/rand"
12 "runtime"
13 "slices"
14 "strconv"
15 "strings"
16 "testing"
17 "unsafe"
18 )
19
20 var mapbench = flag.Bool("mapbench", false, "enable the full set of map benchmark variants")
21
22 const size = 10
23
24 func BenchmarkHashStringSpeed(b *testing.B) {
25 strings := make([]string, size)
26 for i := 0; i < size; i++ {
27 strings[i] = fmt.Sprintf("string#%d", i)
28 }
29 sum := 0
30 m := make(map[string]int, size)
31 for i := 0; i < size; i++ {
32 m[strings[i]] = 0
33 }
34 idx := 0
35 b.ResetTimer()
36 for i := 0; i < b.N; i++ {
37 sum += m[strings[idx]]
38 idx++
39 if idx == size {
40 idx = 0
41 }
42 }
43 }
44
45 type chunk [17]byte
46
47 func BenchmarkHashBytesSpeed(b *testing.B) {
48
49 var chunks [size]chunk
50
51 for i := 0; i < size; i++ {
52 chunks[i][0] = byte(i)
53 }
54
55 m := make(map[chunk]int, size)
56 for i, c := range chunks {
57 m[c] = i
58 }
59 idx := 0
60 b.ResetTimer()
61 for i := 0; i < b.N; i++ {
62 if m[chunks[idx]] != idx {
63 b.Error("bad map entry for chunk")
64 }
65 idx++
66 if idx == size {
67 idx = 0
68 }
69 }
70 }
71
72 func BenchmarkHashInt32Speed(b *testing.B) {
73 ints := make([]int32, size)
74 for i := 0; i < size; i++ {
75 ints[i] = int32(i)
76 }
77 sum := 0
78 m := make(map[int32]int, size)
79 for i := 0; i < size; i++ {
80 m[ints[i]] = 0
81 }
82 idx := 0
83 b.ResetTimer()
84 for i := 0; i < b.N; i++ {
85 sum += m[ints[idx]]
86 idx++
87 if idx == size {
88 idx = 0
89 }
90 }
91 }
92
93 func BenchmarkHashInt64Speed(b *testing.B) {
94 ints := make([]int64, size)
95 for i := 0; i < size; i++ {
96 ints[i] = int64(i)
97 }
98 sum := 0
99 m := make(map[int64]int, size)
100 for i := 0; i < size; i++ {
101 m[ints[i]] = 0
102 }
103 idx := 0
104 b.ResetTimer()
105 for i := 0; i < b.N; i++ {
106 sum += m[ints[idx]]
107 idx++
108 if idx == size {
109 idx = 0
110 }
111 }
112 }
113 func BenchmarkHashStringArraySpeed(b *testing.B) {
114 stringpairs := make([][2]string, size)
115 for i := 0; i < size; i++ {
116 for j := 0; j < 2; j++ {
117 stringpairs[i][j] = fmt.Sprintf("string#%d/%d", i, j)
118 }
119 }
120 sum := 0
121 m := make(map[[2]string]int, size)
122 for i := 0; i < size; i++ {
123 m[stringpairs[i]] = 0
124 }
125 idx := 0
126 b.ResetTimer()
127 for i := 0; i < b.N; i++ {
128 sum += m[stringpairs[idx]]
129 idx++
130 if idx == size {
131 idx = 0
132 }
133 }
134 }
135
136 func BenchmarkMegMap(b *testing.B) {
137 m := make(map[string]bool)
138 for suffix := 'A'; suffix <= 'G'; suffix++ {
139 m[strings.Repeat("X", 1<<20-1)+fmt.Sprint(suffix)] = true
140 }
141 key := strings.Repeat("X", 1<<20-1) + "k"
142 b.ResetTimer()
143 for i := 0; i < b.N; i++ {
144 _, _ = m[key]
145 }
146 }
147
148 func BenchmarkMegOneMap(b *testing.B) {
149 m := make(map[string]bool)
150 m[strings.Repeat("X", 1<<20)] = true
151 key := strings.Repeat("Y", 1<<20)
152 b.ResetTimer()
153 for i := 0; i < b.N; i++ {
154 _, _ = m[key]
155 }
156 }
157
158 func BenchmarkMegEqMap(b *testing.B) {
159 m := make(map[string]bool)
160 key1 := strings.Repeat("X", 1<<20)
161 key2 := strings.Repeat("X", 1<<20)
162 m[key1] = true
163 b.ResetTimer()
164 for i := 0; i < b.N; i++ {
165 _, _ = m[key2]
166 }
167 }
168
169 func BenchmarkMegEmptyMap(b *testing.B) {
170 m := make(map[string]bool)
171 key := strings.Repeat("X", 1<<20)
172 b.ResetTimer()
173 for i := 0; i < b.N; i++ {
174 _, _ = m[key]
175 }
176 }
177
178 func BenchmarkMegEmptyMapWithInterfaceKey(b *testing.B) {
179 m := make(map[any]bool)
180 key := strings.Repeat("X", 1<<20)
181 b.ResetTimer()
182 for i := 0; i < b.N; i++ {
183 _, _ = m[key]
184 }
185 }
186
187 func BenchmarkSmallStrMap(b *testing.B) {
188 m := make(map[string]bool)
189 for suffix := 'A'; suffix <= 'G'; suffix++ {
190 m[fmt.Sprint(suffix)] = true
191 }
192 key := "k"
193 b.ResetTimer()
194 for i := 0; i < b.N; i++ {
195 _, _ = m[key]
196 }
197 }
198
199 func BenchmarkMapStringKeysEight_16(b *testing.B) { benchmarkMapStringKeysEight(b, 16) }
200 func BenchmarkMapStringKeysEight_32(b *testing.B) { benchmarkMapStringKeysEight(b, 32) }
201 func BenchmarkMapStringKeysEight_64(b *testing.B) { benchmarkMapStringKeysEight(b, 64) }
202 func BenchmarkMapStringKeysEight_1M(b *testing.B) { benchmarkMapStringKeysEight(b, 1<<20) }
203
204 func benchmarkMapStringKeysEight(b *testing.B, keySize int) {
205 m := make(map[string]bool)
206 for i := 0; i < 8; i++ {
207 m[strings.Repeat("K", i+1)] = true
208 }
209 key := strings.Repeat("K", keySize)
210 b.ResetTimer()
211 for i := 0; i < b.N; i++ {
212 _ = m[key]
213 }
214 }
215
216 func BenchmarkMapFirst(b *testing.B) {
217 for n := 1; n <= 16; n++ {
218 b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
219 m := make(map[int]bool)
220 for i := 0; i < n; i++ {
221 m[i] = true
222 }
223 b.ResetTimer()
224 for i := 0; i < b.N; i++ {
225 _ = m[0]
226 }
227 })
228 }
229 }
230 func BenchmarkMapMid(b *testing.B) {
231 for n := 1; n <= 16; n++ {
232 b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
233 m := make(map[int]bool)
234 for i := 0; i < n; i++ {
235 m[i] = true
236 }
237 b.ResetTimer()
238 for i := 0; i < b.N; i++ {
239 _ = m[n>>1]
240 }
241 })
242 }
243 }
244 func BenchmarkMapLast(b *testing.B) {
245 for n := 1; n <= 16; n++ {
246 b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
247 m := make(map[int]bool)
248 for i := 0; i < n; i++ {
249 m[i] = true
250 }
251 b.ResetTimer()
252 for i := 0; i < b.N; i++ {
253 _ = m[n-1]
254 }
255 })
256 }
257 }
258
259 func BenchmarkMapCycle(b *testing.B) {
260
261
262
263 const N = 3127
264 p := rand.New(rand.NewSource(1)).Perm(N)
265 m := map[int]int{}
266 for i := 0; i < N; i++ {
267 m[i] = p[i]
268 }
269 b.ResetTimer()
270 j := 0
271 for i := 0; i < b.N; i++ {
272 j = m[j]
273 }
274 sink = uint64(j)
275 }
276
277
278 func benchmarkRepeatedLookup(b *testing.B, lookupKeySize int) {
279 m := make(map[string]bool)
280
281 for i := 0; i < 64; i++ {
282 m[fmt.Sprintf("some key %d", i)] = true
283 }
284 base := strings.Repeat("x", lookupKeySize-1)
285 key1 := base + "1"
286 key2 := base + "2"
287 b.ResetTimer()
288 for i := 0; i < b.N/4; i++ {
289 _ = m[key1]
290 _ = m[key1]
291 _ = m[key2]
292 _ = m[key2]
293 }
294 }
295
296 func BenchmarkRepeatedLookupStrMapKey32(b *testing.B) { benchmarkRepeatedLookup(b, 32) }
297 func BenchmarkRepeatedLookupStrMapKey1M(b *testing.B) { benchmarkRepeatedLookup(b, 1<<20) }
298
299 func BenchmarkMakeMap(b *testing.B) {
300 b.Run("[Byte]Byte", func(b *testing.B) {
301 var m map[byte]byte
302 for i := 0; i < b.N; i++ {
303 m = make(map[byte]byte, 10)
304 }
305 hugeSink = m
306 })
307 b.Run("[Int]Int", func(b *testing.B) {
308 var m map[int]int
309 for i := 0; i < b.N; i++ {
310 m = make(map[int]int, 10)
311 }
312 hugeSink = m
313 })
314 }
315
316 func BenchmarkNewEmptyMap(b *testing.B) {
317 b.ReportAllocs()
318 for i := 0; i < b.N; i++ {
319 _ = make(map[int]int)
320 }
321 }
322
323 func BenchmarkNewSmallMap(b *testing.B) {
324 b.ReportAllocs()
325 for i := 0; i < b.N; i++ {
326 m := make(map[int]int)
327 m[0] = 0
328 m[1] = 1
329 }
330 }
331
332 func BenchmarkSameLengthMap(b *testing.B) {
333
334
335 m := make(map[string]bool)
336 s1 := "foo" + strings.Repeat("-", 100) + "bar"
337 s2 := "goo" + strings.Repeat("-", 100) + "ber"
338 m[s1] = true
339 m[s2] = true
340 b.ResetTimer()
341 for i := 0; i < b.N; i++ {
342 _ = m[s1]
343 }
344 }
345
346 func BenchmarkSmallKeyMap(b *testing.B) {
347 m := make(map[int16]bool)
348 m[5] = true
349 for i := 0; i < b.N; i++ {
350 _ = m[5]
351 }
352 }
353
354 func BenchmarkMapPopulate(b *testing.B) {
355 for size := 1; size < 1000000; size *= 10 {
356 b.Run(strconv.Itoa(size), func(b *testing.B) {
357 b.ReportAllocs()
358 for i := 0; i < b.N; i++ {
359 m := make(map[int]bool)
360 for j := 0; j < size; j++ {
361 m[j] = true
362 }
363 }
364 })
365 }
366 }
367
368 type ComplexAlgKey struct {
369 a, b, c int64
370 _ int
371 d int32
372 _ int
373 e string
374 _ int
375 f, g, h int64
376 }
377
378 func BenchmarkComplexAlgMap(b *testing.B) {
379 m := make(map[ComplexAlgKey]bool)
380 var k ComplexAlgKey
381 m[k] = true
382 for i := 0; i < b.N; i++ {
383 _ = m[k]
384 }
385 }
386
387 func BenchmarkGoMapClear(b *testing.B) {
388 b.Run("Reflexive", func(b *testing.B) {
389 for size := 1; size < 100000; size *= 10 {
390 b.Run(strconv.Itoa(size), func(b *testing.B) {
391 m := make(map[int]int, size)
392 for i := 0; i < b.N; i++ {
393 m[0] = size
394 clear(m)
395 }
396 })
397 }
398 })
399 b.Run("NonReflexive", func(b *testing.B) {
400 for size := 1; size < 100000; size *= 10 {
401 b.Run(strconv.Itoa(size), func(b *testing.B) {
402 m := make(map[float64]int, size)
403 for i := 0; i < b.N; i++ {
404 m[1.0] = size
405 clear(m)
406 }
407 })
408 }
409 })
410 }
411
412 func BenchmarkMapStringConversion(b *testing.B) {
413 for _, length := range []int{32, 64} {
414 b.Run(strconv.Itoa(length), func(b *testing.B) {
415 bytes := make([]byte, length)
416 b.Run("simple", func(b *testing.B) {
417 b.ReportAllocs()
418 m := make(map[string]int)
419 m[string(bytes)] = 0
420 for i := 0; i < b.N; i++ {
421 _ = m[string(bytes)]
422 }
423 })
424 b.Run("struct", func(b *testing.B) {
425 b.ReportAllocs()
426 type stringstruct struct{ s string }
427 m := make(map[stringstruct]int)
428 m[stringstruct{string(bytes)}] = 0
429 for i := 0; i < b.N; i++ {
430 _ = m[stringstruct{string(bytes)}]
431 }
432 })
433 b.Run("array", func(b *testing.B) {
434 b.ReportAllocs()
435 type stringarray [1]string
436 m := make(map[stringarray]int)
437 m[stringarray{string(bytes)}] = 0
438 for i := 0; i < b.N; i++ {
439 _ = m[stringarray{string(bytes)}]
440 }
441 })
442 })
443 }
444 }
445
446 var BoolSink bool
447
448 func BenchmarkMapInterfaceString(b *testing.B) {
449 m := map[any]bool{}
450
451 for i := 0; i < 100; i++ {
452 m[fmt.Sprintf("%d", i)] = true
453 }
454
455 key := (any)("A")
456 b.ResetTimer()
457 for i := 0; i < b.N; i++ {
458 BoolSink = m[key]
459 }
460 }
461 func BenchmarkMapInterfacePtr(b *testing.B) {
462 m := map[any]bool{}
463
464 for i := 0; i < 100; i++ {
465 i := i
466 m[&i] = true
467 }
468
469 key := new(int)
470 b.ResetTimer()
471 for i := 0; i < b.N; i++ {
472 BoolSink = m[key]
473 }
474 }
475
476 var (
477 hintLessThan8 = 7
478 hintGreaterThan8 = 32
479 )
480
481 func BenchmarkNewEmptyMapHintLessThan8(b *testing.B) {
482 b.ReportAllocs()
483 for i := 0; i < b.N; i++ {
484 _ = make(map[int]int, hintLessThan8)
485 }
486 }
487
488 func BenchmarkNewEmptyMapHintGreaterThan8(b *testing.B) {
489 b.ReportAllocs()
490 for i := 0; i < b.N; i++ {
491 _ = make(map[int]int, hintGreaterThan8)
492 }
493 }
494
495 func benchSizes(f func(b *testing.B, n int)) func(*testing.B) {
496 var cases = []int{
497 0,
498 6,
499 12,
500 18,
501 24,
502 30,
503 64,
504 128,
505 256,
506 512,
507 1024,
508 2048,
509 4096,
510 8192,
511 1 << 16,
512 1 << 18,
513 1 << 20,
514 1 << 22,
515 }
516
517
518
519
520
521
522 byDefault := map[int]bool{
523 6: true,
524 64: true,
525 1 << 16: true,
526 }
527
528 return func(b *testing.B) {
529 for _, n := range cases {
530 b.Run("len="+strconv.Itoa(n), func(b *testing.B) {
531 if !*mapbench && !byDefault[n] {
532 b.Skip("Skipped because -mapbench=false")
533 }
534
535 f(b, n)
536 })
537 }
538 }
539 }
540
541
542 type smallType [16]byte
543
544
545 type mediumType [1 << 9]byte
546
547
548 type bigType [1 << 12]byte
549
550 type mapBenchmarkKeyType interface {
551 int32 | int64 | string | smallType | mediumType | bigType | *int32
552 }
553
554 type mapBenchmarkElemType interface {
555 mapBenchmarkKeyType | []int32
556 }
557
558 func genIntValues[T int | int32 | int64](start, end int) []T {
559 vals := make([]T, 0, end-start)
560 for i := start; i < end; i++ {
561 vals = append(vals, T(i))
562 }
563 return vals
564 }
565
566 func genStringValues(start, end int) []string {
567 vals := make([]string, 0, end-start)
568 for i := start; i < end; i++ {
569 vals = append(vals, strconv.Itoa(i))
570 }
571 return vals
572 }
573
574 func genSmallValues(start, end int) []smallType {
575 vals := make([]smallType, 0, end-start)
576 for i := start; i < end; i++ {
577 var v smallType
578 binary.NativeEndian.PutUint64(v[:], uint64(i))
579 vals = append(vals, v)
580 }
581 return vals
582 }
583
584 func genMediumValues(start, end int) []mediumType {
585 vals := make([]mediumType, 0, end-start)
586 for i := start; i < end; i++ {
587 var v mediumType
588 binary.NativeEndian.PutUint64(v[:], uint64(i))
589 vals = append(vals, v)
590 }
591 return vals
592 }
593
594 func genBigValues(start, end int) []bigType {
595 vals := make([]bigType, 0, end-start)
596 for i := start; i < end; i++ {
597 var v bigType
598 binary.NativeEndian.PutUint64(v[:], uint64(i))
599 vals = append(vals, v)
600 }
601 return vals
602 }
603
604 func genPtrValues[T any](start, end int) []*T {
605
606
607 vals := make([]*T, 0, end-start)
608 for i := start; i < end; i++ {
609 v := new(T)
610 vals = append(vals, v)
611 }
612 return vals
613 }
614
615 func genIntSliceValues[T int | int32 | int64](start, end int) [][]T {
616 vals := make([][]T, 0, end-start)
617 for i := start; i < end; i++ {
618 vals = append(vals, []T{T(i)})
619 }
620 return vals
621 }
622
623 func genValues[T mapBenchmarkElemType](start, end int) []T {
624 var t T
625 switch any(t).(type) {
626 case int32:
627 return any(genIntValues[int32](start, end)).([]T)
628 case int64:
629 return any(genIntValues[int64](start, end)).([]T)
630 case string:
631 return any(genStringValues(start, end)).([]T)
632 case smallType:
633 return any(genSmallValues(start, end)).([]T)
634 case mediumType:
635 return any(genMediumValues(start, end)).([]T)
636 case bigType:
637 return any(genBigValues(start, end)).([]T)
638 case *int32:
639 return any(genPtrValues[int32](start, end)).([]T)
640 case []int32:
641 return any(genIntSliceValues[int32](start, end)).([]T)
642 default:
643 panic("unreachable")
644 }
645 }
646
647
648
649
650 func newSink[T mapBenchmarkElemType]() *T {
651 return new(T)
652 }
653
654
655 func fillMap[K mapBenchmarkKeyType, E mapBenchmarkElemType](keys []K, elems []E) map[K]E {
656 m := make(map[K]E, len(keys))
657 for i := range keys {
658 m[keys[i]] = elems[i]
659 }
660 return m
661 }
662
663 func iterCount(b *testing.B, n int) int {
664
665
666
667
668
669 if n == 0 {
670 return b.N
671 }
672 return b.N / n
673 }
674
675 func checkAllocSize[K, E any](b *testing.B, n int) {
676 var k K
677 size := uint64(n) * uint64(unsafe.Sizeof(k))
678 var e E
679 size += uint64(n) * uint64(unsafe.Sizeof(e))
680
681 if size >= 1<<30 {
682 b.Skipf("Total key+elem size %d exceeds 1GiB", size)
683 }
684 }
685
686 func benchmarkMapIter[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
687 checkAllocSize[K, E](b, n)
688 k := genValues[K](0, n)
689 e := genValues[E](0, n)
690 m := fillMap(k, e)
691 iterations := iterCount(b, n)
692 sinkK := newSink[K]()
693 sinkE := newSink[E]()
694 b.ResetTimer()
695
696 for i := 0; i < iterations; i++ {
697 for k, e := range m {
698 *sinkK = k
699 *sinkE = e
700 }
701 }
702 }
703
704 func BenchmarkMapIter(b *testing.B) {
705 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapIter[int32, int32]))
706 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapIter[int64, int64]))
707 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapIter[string, string]))
708 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapIter[smallType, int32]))
709 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapIter[mediumType, int32]))
710 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapIter[bigType, int32]))
711 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapIter[bigType, bigType]))
712 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapIter[int32, bigType]))
713 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapIter[*int32, int32]))
714 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapIter[int32, *int32]))
715 }
716
717 func benchmarkMapIterLowLoad[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
718
719 k := genValues[K](0, 1)
720 e := genValues[E](0, 1)
721
722 m := make(map[K]E, n)
723 for i := range k {
724 m[k[i]] = e[i]
725 }
726
727 iterations := iterCount(b, n)
728 sinkK := newSink[K]()
729 sinkE := newSink[E]()
730 b.ResetTimer()
731
732 for i := 0; i < iterations; i++ {
733 for k, e := range m {
734 *sinkK = k
735 *sinkE = e
736 }
737 }
738 }
739
740 func BenchmarkMapIterLowLoad(b *testing.B) {
741 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapIterLowLoad[int32, int32]))
742 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapIterLowLoad[int64, int64]))
743 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapIterLowLoad[string, string]))
744 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapIterLowLoad[smallType, int32]))
745 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapIterLowLoad[mediumType, int32]))
746 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapIterLowLoad[bigType, int32]))
747 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapIterLowLoad[bigType, bigType]))
748 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapIterLowLoad[int32, bigType]))
749 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapIterLowLoad[*int32, int32]))
750 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapIterLowLoad[int32, *int32]))
751 }
752
753 func benchmarkMapAccessHit[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
754 if n == 0 {
755 b.Skip("can't access empty map")
756 }
757 checkAllocSize[K, E](b, n)
758 k := genValues[K](0, n)
759 e := genValues[E](0, n)
760 m := fillMap(k, e)
761 sink := newSink[E]()
762 b.ResetTimer()
763
764 for i := 0; i < b.N; i++ {
765 *sink = m[k[i%n]]
766 }
767 }
768
769 func BenchmarkMapAccessHit(b *testing.B) {
770 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAccessHit[int32, int32]))
771 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAccessHit[int64, int64]))
772 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAccessHit[string, string]))
773 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAccessHit[smallType, int32]))
774 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAccessHit[mediumType, int32]))
775 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAccessHit[bigType, int32]))
776 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAccessHit[bigType, bigType]))
777 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAccessHit[int32, bigType]))
778 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAccessHit[*int32, int32]))
779 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAccessHit[int32, *int32]))
780 }
781
782 var sinkOK bool
783
784 func benchmarkMapAccessMiss[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
785 checkAllocSize[K, E](b, n)
786 k := genValues[K](0, n)
787 e := genValues[E](0, n)
788 m := fillMap(k, e)
789 if n == 0 {
790 n = 1
791 }
792 w := genValues[K](n, 2*n)
793 b.ResetTimer()
794
795 var ok bool
796 for i := 0; i < b.N; i++ {
797 _, ok = m[w[i%n]]
798 }
799
800 sinkOK = ok
801 }
802
803 func BenchmarkMapAccessMiss(b *testing.B) {
804 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAccessMiss[int32, int32]))
805 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAccessMiss[int64, int64]))
806 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAccessMiss[string, string]))
807 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAccessMiss[smallType, int32]))
808 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAccessMiss[mediumType, int32]))
809 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAccessMiss[bigType, int32]))
810 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAccessMiss[bigType, bigType]))
811 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAccessMiss[int32, bigType]))
812 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAccessMiss[*int32, int32]))
813 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAccessMiss[int32, *int32]))
814 }
815
816
817 func benchmarkMapAssignExists[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
818 if n == 0 {
819 b.Skip("can't assign to existing keys in empty map")
820 }
821 checkAllocSize[K, E](b, n)
822 k := genValues[K](0, n)
823 e := genValues[E](0, n)
824 m := fillMap(k, e)
825 b.ResetTimer()
826
827 for i := 0; i < b.N; i++ {
828 m[k[i%n]] = e[i%n]
829 }
830 }
831
832 func BenchmarkMapAssignExists(b *testing.B) {
833 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignExists[int32, int32]))
834 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignExists[int64, int64]))
835 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignExists[string, string]))
836 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignExists[smallType, int32]))
837 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignExists[mediumType, int32]))
838 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignExists[bigType, int32]))
839 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignExists[bigType, bigType]))
840 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignExists[int32, bigType]))
841 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignExists[*int32, int32]))
842 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignExists[int32, *int32]))
843 }
844
845
846
847
848
849
850
851 func benchmarkMapAssignFillNoHint[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
852 if n == 0 {
853 b.Skip("can't create empty map via assignment")
854 }
855 checkAllocSize[K, E](b, n)
856 k := genValues[K](0, n)
857 e := genValues[E](0, n)
858 b.ResetTimer()
859
860 var m map[K]E
861 for i := 0; i < b.N; i++ {
862 if i%n == 0 {
863 m = make(map[K]E)
864 }
865 m[k[i%n]] = e[i%n]
866 }
867 }
868
869 func BenchmarkMapAssignFillNoHint(b *testing.B) {
870 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[int32, int32]))
871 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignFillNoHint[int64, int64]))
872 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignFillNoHint[string, string]))
873 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[smallType, int32]))
874 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[mediumType, int32]))
875 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[bigType, int32]))
876 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignFillNoHint[bigType, bigType]))
877 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignFillNoHint[int32, bigType]))
878 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignFillNoHint[*int32, int32]))
879 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignFillNoHint[int32, *int32]))
880 }
881
882
883
884 func benchmarkMapAssignGrowLatency[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
885 if n == 0 {
886 b.Skip("can't create empty map via assignment")
887 }
888 checkAllocSize[K, E](b, n)
889 k := genValues[K](0, n)
890 e := genValues[E](0, n)
891
892
893
894
895 sample := make([]int64, b.N)
896
897 b.ResetTimer()
898
899 var m map[K]E
900 for i := 0; i < b.N; i++ {
901 if i%n == 0 {
902 m = make(map[K]E)
903 }
904 start := runtime.Nanotime()
905 m[k[i%n]] = e[i%n]
906 end := runtime.Nanotime()
907 sample[i] = end - start
908 }
909
910 b.StopTimer()
911
912 slices.Sort(sample)
913
914
915
916 b.ReportMetric(float64(sample[int(float64(len(sample))*0.5)]), "p50-ns/op")
917 b.ReportMetric(float64(sample[int(float64(len(sample))*0.99)]), "p99-ns/op")
918 b.ReportMetric(float64(sample[int(float64(len(sample))*0.999)]), "p99.9-ns/op")
919 b.ReportMetric(float64(sample[int(float64(len(sample))*0.9999)]), "p99.99-ns/op")
920 b.ReportMetric(float64(sample[len(sample)-1]), "p100-ns/op")
921 }
922
923 func BenchmarkMapAssignGrowLatency(b *testing.B) {
924 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[int32, int32]))
925 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignGrowLatency[int64, int64]))
926 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignGrowLatency[string, string]))
927 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[smallType, int32]))
928 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[mediumType, int32]))
929 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[bigType, int32]))
930 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignGrowLatency[bigType, bigType]))
931 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignGrowLatency[int32, bigType]))
932 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignGrowLatency[*int32, int32]))
933 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignGrowLatency[int32, *int32]))
934 }
935
936
937
938
939
940 func benchmarkMapAssignFillHint[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
941 if n == 0 {
942 b.Skip("can't create empty map via assignment")
943 }
944 checkAllocSize[K, E](b, n)
945 k := genValues[K](0, n)
946 e := genValues[E](0, n)
947 b.ResetTimer()
948
949 var m map[K]E
950 for i := 0; i < b.N; i++ {
951 if i%n == 0 {
952 m = make(map[K]E, n)
953 }
954 m[k[i%n]] = e[i%n]
955 }
956 }
957
958 func BenchmarkMapAssignFillHint(b *testing.B) {
959 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignFillHint[int32, int32]))
960 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignFillHint[int64, int64]))
961 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignFillHint[string, string]))
962 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignFillHint[smallType, int32]))
963 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignFillHint[mediumType, int32]))
964 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignFillHint[bigType, int32]))
965 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignFillHint[bigType, bigType]))
966 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignFillHint[int32, bigType]))
967 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignFillHint[*int32, int32]))
968 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignFillHint[int32, *int32]))
969 }
970
971
972
973
974
975 func benchmarkMapAssignFillClear[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
976 if n == 0 {
977 b.Skip("can't create empty map via assignment")
978 }
979 checkAllocSize[K, E](b, n)
980 k := genValues[K](0, n)
981 e := genValues[E](0, n)
982 m := fillMap(k, e)
983 b.ResetTimer()
984
985 for i := 0; i < b.N; i++ {
986 if i%n == 0 {
987 clear(m)
988 }
989 m[k[i%n]] = e[i%n]
990 }
991 }
992
993 func BenchmarkMapAssignFillClear(b *testing.B) {
994 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignFillClear[int32, int32]))
995 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignFillClear[int64, int64]))
996 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignFillClear[string, string]))
997 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignFillClear[smallType, int32]))
998 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignFillClear[mediumType, int32]))
999 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignFillClear[bigType, int32]))
1000 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapAssignFillClear[bigType, bigType]))
1001 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapAssignFillClear[int32, bigType]))
1002 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapAssignFillClear[*int32, int32]))
1003 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapAssignFillClear[int32, *int32]))
1004 }
1005
1006
1007 func benchmarkMapAssignAddition[K mapBenchmarkKeyType, E int32 | int64 | string](b *testing.B, n int) {
1008 if n == 0 {
1009 b.Skip("can't modify empty map via assignment")
1010 }
1011 checkAllocSize[K, E](b, n)
1012 k := genValues[K](0, n)
1013 e := genValues[E](0, n)
1014 m := fillMap(k, e)
1015 b.ResetTimer()
1016
1017 for i := 0; i < b.N; i++ {
1018 m[k[i%n]] += e[i%n]
1019 }
1020 }
1021
1022 func BenchmarkMapAssignAddition(b *testing.B) {
1023 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapAssignAddition[int32, int32]))
1024 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapAssignAddition[int64, int64]))
1025 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapAssignAddition[string, string]))
1026 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapAssignAddition[smallType, int32]))
1027 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapAssignAddition[mediumType, int32]))
1028 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapAssignAddition[bigType, int32]))
1029 }
1030
1031
1032 func benchmarkMapAssignAppend[K mapBenchmarkKeyType](b *testing.B, n int) {
1033 if n == 0 {
1034 b.Skip("can't modify empty map via append")
1035 }
1036 checkAllocSize[K, []int32](b, n)
1037 k := genValues[K](0, n)
1038 e := genValues[[]int32](0, n)
1039 m := fillMap(k, e)
1040 b.ResetTimer()
1041
1042 for i := 0; i < b.N; i++ {
1043 m[k[i%n]] = append(m[k[i%n]], e[i%n][0])
1044 }
1045 }
1046
1047 func BenchmarkMapAssignAppend(b *testing.B) {
1048 b.Run("Key=int32/Elem=[]int32", benchSizes(benchmarkMapAssignAppend[int32]))
1049 b.Run("Key=int64/Elem=[]int32", benchSizes(benchmarkMapAssignAppend[int64]))
1050 b.Run("Key=string/Elem=[]int32", benchSizes(benchmarkMapAssignAppend[string]))
1051 }
1052
1053 func benchmarkMapDelete[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
1054 if n == 0 {
1055 b.Skip("can't delete from empty map")
1056 }
1057 checkAllocSize[K, E](b, n)
1058 k := genValues[K](0, n)
1059 e := genValues[E](0, n)
1060 m := fillMap(k, e)
1061 b.ResetTimer()
1062
1063 for i := 0; i < b.N; i++ {
1064 if len(m) == 0 {
1065
1066
1067
1068 for j := range k {
1069 m[k[j]] = e[j]
1070 }
1071 }
1072 delete(m, k[i%n])
1073 }
1074 }
1075
1076 func BenchmarkMapDelete(b *testing.B) {
1077 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapDelete[int32, int32]))
1078 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapDelete[int64, int64]))
1079 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapDelete[string, string]))
1080 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapDelete[smallType, int32]))
1081 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapDelete[mediumType, int32]))
1082 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapDelete[bigType, int32]))
1083 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapDelete[bigType, bigType]))
1084 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapDelete[int32, bigType]))
1085 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapDelete[*int32, int32]))
1086 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapDelete[int32, *int32]))
1087 }
1088
1089
1090
1091 func benchmarkMapPop[K mapBenchmarkKeyType, E mapBenchmarkElemType](b *testing.B, n int) {
1092 if n == 0 {
1093 b.Skip("can't delete from empty map")
1094 }
1095 checkAllocSize[K, E](b, n)
1096 k := genValues[K](0, n)
1097 e := genValues[E](0, n)
1098 m := fillMap(k, e)
1099 b.ResetTimer()
1100
1101 for i := 0; i < b.N; i++ {
1102 if len(m) == 0 {
1103
1104
1105
1106 for j := range k {
1107 m[k[j]] = e[j]
1108 }
1109 }
1110 for key := range m {
1111 delete(m, key)
1112 break
1113 }
1114 }
1115 }
1116
1117 func BenchmarkMapPop(b *testing.B) {
1118 b.Run("Key=int32/Elem=int32", benchSizes(benchmarkMapPop[int32, int32]))
1119 b.Run("Key=int64/Elem=int64", benchSizes(benchmarkMapPop[int64, int64]))
1120 b.Run("Key=string/Elem=string", benchSizes(benchmarkMapPop[string, string]))
1121 b.Run("Key=smallType/Elem=int32", benchSizes(benchmarkMapPop[smallType, int32]))
1122 b.Run("Key=mediumType/Elem=int32", benchSizes(benchmarkMapPop[mediumType, int32]))
1123 b.Run("Key=bigType/Elem=int32", benchSizes(benchmarkMapPop[bigType, int32]))
1124 b.Run("Key=bigType/Elem=bigType", benchSizes(benchmarkMapPop[bigType, bigType]))
1125 b.Run("Key=int32/Elem=bigType", benchSizes(benchmarkMapPop[int32, bigType]))
1126 b.Run("Key=*int32/Elem=int32", benchSizes(benchmarkMapPop[*int32, int32]))
1127 b.Run("Key=int32/Elem=*int32", benchSizes(benchmarkMapPop[int32, *int32]))
1128 }
1129
1130 func BenchmarkMapDeleteLargeKey(b *testing.B) {
1131 m := map[string]int{}
1132 for i := range 9 {
1133 m[fmt.Sprintf("%d", i)] = i
1134 }
1135 key := strings.Repeat("*", 10000)
1136 for range b.N {
1137 delete(m, key)
1138 }
1139 }
1140
View as plain text