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 }