github.com/cockroachdb/swiss@v0.0.0-20240303172742-c161743eb608/bench_test.go (about)

     1  // Copyright 2024 The Cockroach Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package swiss
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"strconv"
    21  	"testing"
    22  
    23  	"github.com/aclements/go-perfevent/perfbench"
    24  )
    25  
    26  func BenchmarkMapIter(b *testing.B) {
    27  	b.Run("impl=runtimeMap", func(b *testing.B) {
    28  		b.Run("t=Int", benchSizes(benchmarkRuntimeMapIter[int64], genKeys[int64]))
    29  	})
    30  	b.Run("impl=swissMap", func(b *testing.B) {
    31  		b.Run("t=Int", benchSizes(benchmarkSwissMapIter[int64], genKeys[int64]))
    32  	})
    33  }
    34  
    35  func BenchmarkMapGetHit(b *testing.B) {
    36  	b.Run("impl=runtimeMap", func(b *testing.B) {
    37  		b.Run("t=Int64", benchSizes(benchmarkRuntimeMapGetHit[int64], genKeys[int64]))
    38  		b.Run("t=Int32", benchSizes(benchmarkRuntimeMapGetHit[int32], genKeys[int32]))
    39  		b.Run("t=String", benchSizes(benchmarkRuntimeMapGetHit[string], genKeys[string]))
    40  	})
    41  	b.Run("impl=swissMap", func(b *testing.B) {
    42  		b.Run("t=Int64", benchSizes(benchmarkSwissMapGetHit[int64], genKeys[int64]))
    43  		b.Run("t=Int32", benchSizes(benchmarkSwissMapGetHit[int32], genKeys[int32]))
    44  		b.Run("t=String", benchSizes(benchmarkSwissMapGetHit[string], genKeys[string]))
    45  	})
    46  }
    47  
    48  func BenchmarkMapGetMiss(b *testing.B) {
    49  	b.Run("impl=runtimeMap", func(b *testing.B) {
    50  		b.Run("t=Int64", benchSizes(benchmarkRuntimeMapGetMiss[int64], genKeys[int64]))
    51  		b.Run("t=Int32", benchSizes(benchmarkRuntimeMapGetMiss[int32], genKeys[int32]))
    52  		b.Run("t=String", benchSizes(benchmarkRuntimeMapGetMiss[string], genKeys[string]))
    53  	})
    54  	b.Run("impl=swissMap", func(b *testing.B) {
    55  		b.Run("t=Int64", benchSizes(benchmarkSwissMapGetMiss[int64], genKeys[int64]))
    56  		b.Run("t=Int32", benchSizes(benchmarkSwissMapGetMiss[int32], genKeys[int32]))
    57  		b.Run("t=String", benchSizes(benchmarkSwissMapGetMiss[string], genKeys[string]))
    58  	})
    59  }
    60  
    61  func BenchmarkMapPutGrow(b *testing.B) {
    62  	b.Run("impl=runtimeMap", func(b *testing.B) {
    63  		b.Run("t=Int64", benchSizes(benchmarkRuntimeMapPutGrow[int64], genKeys[int64]))
    64  		b.Run("t=Int32", benchSizes(benchmarkRuntimeMapPutGrow[int32], genKeys[int32]))
    65  		b.Run("t=String", benchSizes(benchmarkRuntimeMapPutGrow[string], genKeys[string]))
    66  	})
    67  	b.Run("impl=swissMap", func(b *testing.B) {
    68  		b.Run("t=Int64", benchSizes(benchmarkSwissMapPutGrow[int64], genKeys[int64]))
    69  		b.Run("t=Int32", benchSizes(benchmarkSwissMapPutGrow[int32], genKeys[int32]))
    70  		b.Run("t=String", benchSizes(benchmarkSwissMapPutGrow[string], genKeys[string]))
    71  	})
    72  }
    73  
    74  func BenchmarkMapPutPreAllocate(b *testing.B) {
    75  	b.Run("impl=runtimeMap", func(b *testing.B) {
    76  		b.Run("t=Int64", benchSizes(benchmarkRuntimeMapPutPreAllocate[int64], genKeys[int64]))
    77  		b.Run("t=Int32", benchSizes(benchmarkRuntimeMapPutPreAllocate[int32], genKeys[int32]))
    78  		b.Run("t=String", benchSizes(benchmarkRuntimeMapPutPreAllocate[string], genKeys[string]))
    79  	})
    80  	b.Run("impl=swissMap", func(b *testing.B) {
    81  		b.Run("t=Int64", benchSizes(benchmarkSwissMapPutPreAllocate[int64], genKeys[int64]))
    82  		b.Run("t=Int32", benchSizes(benchmarkSwissMapPutPreAllocate[int32], genKeys[int32]))
    83  		b.Run("t=String", benchSizes(benchmarkSwissMapPutPreAllocate[string], genKeys[string]))
    84  	})
    85  }
    86  
    87  func BenchmarkMapPutReuse(b *testing.B) {
    88  	b.Run("impl=runtimeMap", func(b *testing.B) {
    89  		b.Run("t=Int64", benchSizes(benchmarkRuntimeMapPutReuse[int64], genKeys[int64]))
    90  		b.Run("t=Int32", benchSizes(benchmarkRuntimeMapPutReuse[int32], genKeys[int32]))
    91  		b.Run("t=String", benchSizes(benchmarkRuntimeMapPutReuse[string], genKeys[string]))
    92  	})
    93  	b.Run("impl=swissMap", func(b *testing.B) {
    94  		b.Run("t=Int64", benchSizes(benchmarkSwissMapPutReuse[int64], genKeys[int64]))
    95  		b.Run("t=Int32", benchSizes(benchmarkSwissMapPutReuse[int32], genKeys[int32]))
    96  		b.Run("t=String", benchSizes(benchmarkSwissMapPutReuse[string], genKeys[string]))
    97  	})
    98  }
    99  
   100  func BenchmarkMapPutDelete(b *testing.B) {
   101  	b.Run("impl=runtimeMap", func(b *testing.B) {
   102  		b.Run("t=Int64", benchSizes(benchmarkRuntimeMapPutDelete[int64], genKeys[int64]))
   103  		b.Run("t=Int32", benchSizes(benchmarkRuntimeMapPutDelete[int32], genKeys[int32]))
   104  		b.Run("t=String", benchSizes(benchmarkRuntimeMapPutDelete[string], genKeys[string]))
   105  	})
   106  	b.Run("impl=swissMap", func(b *testing.B) {
   107  		b.Run("t=Int64", benchSizes(benchmarkSwissMapPutDelete[int64], genKeys[int64]))
   108  		b.Run("t=Int32", benchSizes(benchmarkSwissMapPutDelete[int32], genKeys[int32]))
   109  		b.Run("t=String", benchSizes(benchmarkSwissMapPutDelete[string], genKeys[string]))
   110  	})
   111  }
   112  
   113  type benchTypes interface {
   114  	int32 | int64 | string
   115  }
   116  
   117  func benchSizes[T benchTypes](
   118  	f func(b *testing.B, n int, genKeys func(start, end int) []T), genKeys func(start, end int) []T,
   119  ) func(*testing.B) {
   120  	var cases = []int{
   121  		6, 12, 18, 24, 30,
   122  		64,
   123  		128,
   124  		256,
   125  		512,
   126  		1024,
   127  		2048,
   128  		4096,
   129  		8192,
   130  		1 << 16,
   131  		1 << 18,
   132  		1 << 20,
   133  		1 << 22,
   134  	}
   135  
   136  	return func(b *testing.B) {
   137  		for _, n := range cases {
   138  			b.Run("len="+strconv.Itoa(n), func(b *testing.B) { f(b, n, genKeys) })
   139  		}
   140  	}
   141  }
   142  
   143  func genKeys[T benchTypes](start, end int) []T {
   144  	var t T
   145  	switch any(t).(type) {
   146  	case int32:
   147  		keys := make([]int32, end-start)
   148  		for i := range keys {
   149  			keys[i] = int32(start + i)
   150  		}
   151  		return unsafeConvertSlice[T](keys)
   152  	case int64:
   153  		keys := make([]int64, end-start)
   154  		for i := range keys {
   155  			keys[i] = int64(start + i)
   156  		}
   157  		return unsafeConvertSlice[T](keys)
   158  	case string:
   159  		keys := make([]string, end-start)
   160  		for i := range keys {
   161  			keys[i] = strconv.Itoa(start + i)
   162  		}
   163  		return unsafeConvertSlice[T](keys)
   164  	default:
   165  		panic("not reached")
   166  	}
   167  }
   168  
   169  func benchmarkRuntimeMapIter[T benchTypes](b *testing.B, n int, genKeys func(start, end int) []T) {
   170  	c := perfbench.Open(b)
   171  
   172  	m := make(map[T]T, n)
   173  	keys := genKeys(0, n)
   174  	for _, k := range keys {
   175  		m[k] = k
   176  	}
   177  	b.ResetTimer()
   178  	c.Reset()
   179  	var tmp T
   180  	for i := 0; i < b.N; i++ {
   181  		for k, v := range m {
   182  			tmp += k + v
   183  		}
   184  	}
   185  }
   186  
   187  func benchmarkSwissMapIter[T benchTypes](b *testing.B, n int, genKeys func(start, end int) []T) {
   188  	c := perfbench.Open(b)
   189  
   190  	m := New[T, T](n)
   191  	keys := genKeys(0, n)
   192  	for _, k := range keys {
   193  		m.Put(k, k)
   194  	}
   195  	b.ResetTimer()
   196  	c.Reset()
   197  	var tmp T
   198  	for i := 0; i < b.N; i++ {
   199  		m.All(func(k, v T) bool {
   200  			tmp += k + v
   201  			return true
   202  		})
   203  	}
   204  }
   205  
   206  func benchmarkRuntimeMapGetMiss[T benchTypes](
   207  	b *testing.B, n int, genKeys func(start, end int) []T,
   208  ) {
   209  	c := perfbench.Open(b)
   210  
   211  	m := make(map[T]T)
   212  	keys := genKeys(0, n)
   213  	miss := genKeys(-n, 0)
   214  	for _, k := range keys {
   215  		m[k] = k
   216  	}
   217  	b.ResetTimer()
   218  	c.Reset()
   219  	for i := 0; i < b.N; i++ {
   220  		_ = m[miss[i%len(miss)]]
   221  	}
   222  }
   223  
   224  func benchmarkSwissMapGetMiss[T comparable](b *testing.B, n int, genKeys func(start, end int) []T) {
   225  	c := perfbench.Open(b)
   226  
   227  	m := New[T, T](0)
   228  	keys := genKeys(0, n)
   229  	miss := genKeys(-n, 0)
   230  	for j := range keys {
   231  		m.Put(keys[j], keys[j])
   232  	}
   233  	b.ResetTimer()
   234  	c.Reset()
   235  	var ok bool
   236  	for i := 0; i < b.N; i++ {
   237  		_, ok = m.Get(miss[i%len(miss)])
   238  	}
   239  	c.Stop()
   240  	b.StopTimer()
   241  	fmt.Fprint(io.Discard, ok)
   242  
   243  	b.ReportMetric(float64(m.Len())/float64(m.capacity()), "load")
   244  
   245  	var fullGroups uint32
   246  	var groupsCount uint32
   247  	m.buckets(0, func(b *bucket[T, T]) bool {
   248  		fullGroups += b.fullGroups()
   249  		groupsCount += b.groupMask + 1
   250  		return true
   251  	})
   252  	b.ReportMetric(100*float64(fullGroups)/float64(groupsCount), "%fullgrp")
   253  }
   254  
   255  func benchmarkRuntimeMapGetHit[T benchTypes](
   256  	b *testing.B, n int, genKeys func(start, end int) []T,
   257  ) {
   258  	c := perfbench.Open(b)
   259  
   260  	m := make(map[T]T, n)
   261  	keys := genKeys(0, n)
   262  	for _, k := range keys {
   263  		m[k] = k
   264  	}
   265  
   266  	// Go's builtin map has an optimization to avoid string comparisons if
   267  	// there is pointer equality. Defeat this optimization to get a better
   268  	// apples-to-apples comparison. This is reasonable to do because looking
   269  	// up a value by a string key which shares the underlying string data with
   270  	// the element in the map is a rare pattern.
   271  	keys = genKeys(0, n)
   272  
   273  	b.ResetTimer()
   274  	c.Reset()
   275  	for i := 0; i < b.N; i++ {
   276  		_ = m[keys[i%n]]
   277  	}
   278  }
   279  
   280  func benchmarkSwissMapGetHit[T benchTypes](b *testing.B, n int, genKeys func(start, end int) []T) {
   281  	c := perfbench.Open(b)
   282  
   283  	m := New[T, T](n)
   284  	keys := genKeys(0, n)
   285  	for _, k := range keys {
   286  		m.Put(k, k)
   287  	}
   288  	b.ResetTimer()
   289  	c.Reset()
   290  	var ok bool
   291  	for i := 0; i < b.N; i++ {
   292  		_, ok = m.Get(keys[i%n])
   293  	}
   294  	c.Stop()
   295  	b.StopTimer()
   296  	fmt.Fprint(io.Discard, ok)
   297  }
   298  
   299  func benchmarkRuntimeMapPutGrow[T benchTypes](
   300  	b *testing.B, n int, genKeys func(start, end int) []T,
   301  ) {
   302  	c := perfbench.Open(b)
   303  
   304  	keys := genKeys(0, n)
   305  	b.ResetTimer()
   306  	c.Reset()
   307  	for i := 0; i < b.N; i++ {
   308  		m := make(map[T]T)
   309  		for _, k := range keys {
   310  			m[k] = k
   311  		}
   312  	}
   313  }
   314  
   315  func benchmarkSwissMapPutGrow[T benchTypes](b *testing.B, n int, genKeys func(start, end int) []T) {
   316  	c := perfbench.Open(b)
   317  
   318  	var m Map[T, T]
   319  	keys := genKeys(0, n)
   320  	b.ResetTimer()
   321  	c.Reset()
   322  	for i := 0; i < b.N; i++ {
   323  		m.Init(0)
   324  		for _, k := range keys {
   325  			m.Put(k, k)
   326  		}
   327  	}
   328  }
   329  
   330  func benchmarkRuntimeMapPutPreAllocate[T benchTypes](
   331  	b *testing.B, n int, genKeys func(start, end int) []T,
   332  ) {
   333  	c := perfbench.Open(b)
   334  
   335  	keys := genKeys(0, n)
   336  	b.ResetTimer()
   337  	c.Reset()
   338  	for i := 0; i < b.N; i++ {
   339  		m := make(map[T]T, n)
   340  		for _, k := range keys {
   341  			m[k] = k
   342  		}
   343  	}
   344  }
   345  
   346  func benchmarkSwissMapPutPreAllocate[T benchTypes](
   347  	b *testing.B, n int, genKeys func(start, end int) []T,
   348  ) {
   349  	c := perfbench.Open(b)
   350  
   351  	var m Map[T, T]
   352  	keys := genKeys(0, n)
   353  	b.ResetTimer()
   354  	c.Reset()
   355  	for i := 0; i < b.N; i++ {
   356  		m.Init(n)
   357  		for _, k := range keys {
   358  			m.Put(k, k)
   359  		}
   360  	}
   361  }
   362  
   363  func benchmarkRuntimeMapPutReuse[T benchTypes](
   364  	b *testing.B, n int, genKeys func(start, end int) []T,
   365  ) {
   366  	c := perfbench.Open(b)
   367  
   368  	m := make(map[T]T, n)
   369  	keys := genKeys(0, n)
   370  	b.ResetTimer()
   371  	c.Reset()
   372  	for i := 0; i < b.N; i++ {
   373  		for _, k := range keys {
   374  			m[k] = k
   375  		}
   376  		for k := range m {
   377  			delete(m, k)
   378  		}
   379  	}
   380  }
   381  
   382  func benchmarkSwissMapPutReuse[T benchTypes](
   383  	b *testing.B, n int, genKeys func(start, end int) []T,
   384  ) {
   385  	c := perfbench.Open(b)
   386  
   387  	m := New[T, T](n)
   388  	keys := genKeys(0, n)
   389  	b.ResetTimer()
   390  	c.Reset()
   391  	for i := 0; i < b.N; i++ {
   392  		for _, k := range keys {
   393  			m.Put(k, k)
   394  		}
   395  		m.Clear()
   396  	}
   397  }
   398  
   399  func benchmarkRuntimeMapPutDelete[T benchTypes](
   400  	b *testing.B, n int, genKeys func(start, end int) []T,
   401  ) {
   402  	c := perfbench.Open(b)
   403  
   404  	m := make(map[T]T, n)
   405  	keys := genKeys(0, n)
   406  	for _, k := range keys {
   407  		m[k] = k
   408  	}
   409  	b.ResetTimer()
   410  	c.Reset()
   411  	for i := 0; i < b.N; i++ {
   412  		j := i % n
   413  		delete(m, keys[j])
   414  		m[keys[j]] = keys[j]
   415  	}
   416  }
   417  
   418  func benchmarkSwissMapPutDelete[T benchTypes](
   419  	b *testing.B, n int, genKeys func(start, end int) []T,
   420  ) {
   421  	c := perfbench.Open(b)
   422  
   423  	m := New[T, T](n)
   424  	keys := genKeys(0, n)
   425  	for _, k := range keys {
   426  		m.Put(k, k)
   427  	}
   428  	b.ResetTimer()
   429  	c.Reset()
   430  	for i := 0; i < b.N; i++ {
   431  		j := i % n
   432  		m.Delete(keys[j])
   433  		m.Put(keys[j], keys[j])
   434  	}
   435  }