github.com/lrita/cmap@v0.0.0-20231108122212-cb084a67f554/cmap_bench_test.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cmap_test
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"sync"
    11  	"sync/atomic"
    12  	"testing"
    13  
    14  	"github.com/lrita/cmap"
    15  )
    16  
    17  type bench struct {
    18  	setup func(*testing.B, mapInterface)
    19  	perG  func(b *testing.B, pb *testing.PB, i int, m mapInterface)
    20  }
    21  
    22  func benchMap(b *testing.B, bench bench) {
    23  	for _, m := range [...]mapInterface{&DeepCopyMap{}, &RWMutexMap{}, &sync.Map{}, &cmap.Cmap{}} {
    24  		b.Run(fmt.Sprintf("%T", m), func(b *testing.B) {
    25  			m = reflect.New(reflect.TypeOf(m).Elem()).Interface().(mapInterface)
    26  			if bench.setup != nil {
    27  				bench.setup(b, m)
    28  			}
    29  
    30  			b.ResetTimer()
    31  
    32  			var i int64
    33  			b.RunParallel(func(pb *testing.PB) {
    34  				id := int(atomic.AddInt64(&i, 1) - 1)
    35  				bench.perG(b, pb, id*b.N, m)
    36  			})
    37  		})
    38  	}
    39  }
    40  
    41  func BenchmarkLoadMostlyHits(b *testing.B) {
    42  	const hits, misses = 1023, 1
    43  
    44  	benchMap(b, bench{
    45  		setup: func(_ *testing.B, m mapInterface) {
    46  			for i := 0; i < hits; i++ {
    47  				m.LoadOrStore(i, i)
    48  			}
    49  			// Prime the map to get it into a steady state.
    50  			for i := 0; i < hits*2; i++ {
    51  				m.Load(i % hits)
    52  			}
    53  		},
    54  
    55  		perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
    56  			for ; pb.Next(); i++ {
    57  				m.Load(i % (hits + misses))
    58  			}
    59  		},
    60  	})
    61  }
    62  
    63  func BenchmarkLoadMostlyMisses(b *testing.B) {
    64  	const hits, misses = 1, 1023
    65  
    66  	benchMap(b, bench{
    67  		setup: func(_ *testing.B, m mapInterface) {
    68  			for i := 0; i < hits; i++ {
    69  				m.LoadOrStore(i, i)
    70  			}
    71  			// Prime the map to get it into a steady state.
    72  			for i := 0; i < hits*2; i++ {
    73  				m.Load(i % hits)
    74  			}
    75  		},
    76  
    77  		perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
    78  			for ; pb.Next(); i++ {
    79  				m.Load(i % (hits + misses))
    80  			}
    81  		},
    82  	})
    83  }
    84  
    85  func BenchmarkLoadOrStoreBalanced(b *testing.B) {
    86  	const hits, misses = 128, 128
    87  
    88  	benchMap(b, bench{
    89  		setup: func(b *testing.B, m mapInterface) {
    90  			if _, ok := m.(*DeepCopyMap); ok {
    91  				b.Skip("DeepCopyMap has quadratic running time.")
    92  			}
    93  			for i := 0; i < hits; i++ {
    94  				m.LoadOrStore(i, i)
    95  			}
    96  			// Prime the map to get it into a steady state.
    97  			for i := 0; i < hits*2; i++ {
    98  				m.Load(i % hits)
    99  			}
   100  		},
   101  
   102  		perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
   103  			for ; pb.Next(); i++ {
   104  				j := i % (hits + misses)
   105  				if j < hits {
   106  					if _, ok := m.LoadOrStore(j, i); !ok {
   107  						b.Fatalf("unexpected miss for %v", j)
   108  					}
   109  				} else {
   110  					if v, loaded := m.LoadOrStore(i, i); loaded {
   111  						b.Fatalf("failed to store %v: existing value %v", i, v)
   112  					}
   113  				}
   114  			}
   115  		},
   116  	})
   117  }
   118  
   119  func BenchmarkLoadOrStoreUnique(b *testing.B) {
   120  	benchMap(b, bench{
   121  		setup: func(b *testing.B, m mapInterface) {
   122  			if _, ok := m.(*DeepCopyMap); ok {
   123  				b.Skip("DeepCopyMap has quadratic running time.")
   124  			}
   125  		},
   126  
   127  		perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
   128  			for ; pb.Next(); i++ {
   129  				m.LoadOrStore(i, i)
   130  			}
   131  		},
   132  	})
   133  }
   134  
   135  func BenchmarkLoadOrStoreCollision(b *testing.B) {
   136  	benchMap(b, bench{
   137  		setup: func(_ *testing.B, m mapInterface) {
   138  			m.LoadOrStore(0, 0)
   139  		},
   140  
   141  		perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
   142  			for ; pb.Next(); i++ {
   143  				m.LoadOrStore(0, 0)
   144  			}
   145  		},
   146  	})
   147  }
   148  
   149  func BenchmarkRange(b *testing.B) {
   150  	const mapSize = 1 << 10
   151  
   152  	benchMap(b, bench{
   153  		setup: func(_ *testing.B, m mapInterface) {
   154  			for i := 0; i < mapSize; i++ {
   155  				m.Store(i, i)
   156  			}
   157  		},
   158  
   159  		perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
   160  			for ; pb.Next(); i++ {
   161  				m.Range(func(_, _ interface{}) bool { return true })
   162  			}
   163  		},
   164  	})
   165  }
   166  
   167  // BenchmarkAdversarialAlloc tests performance when we store a new value
   168  // immediately whenever the map is promoted to clean and otherwise load a
   169  // unique, missing key.
   170  //
   171  // This forces the Load calls to always acquire the map's mutex.
   172  func BenchmarkAdversarialAlloc(b *testing.B) {
   173  	benchMap(b, bench{
   174  		perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
   175  			var stores, loadsSinceStore int64
   176  			for ; pb.Next(); i++ {
   177  				m.Load(i)
   178  				if loadsSinceStore++; loadsSinceStore > stores {
   179  					m.LoadOrStore(i, stores)
   180  					loadsSinceStore = 0
   181  					stores++
   182  				}
   183  			}
   184  		},
   185  	})
   186  }
   187  
   188  // BenchmarkAdversarialDelete tests performance when we periodically delete
   189  // one key and add a different one in a large map.
   190  //
   191  // This forces the Load calls to always acquire the map's mutex and periodically
   192  // makes a full copy of the map despite changing only one entry.
   193  func BenchmarkAdversarialDelete(b *testing.B) {
   194  	const mapSize = 1 << 10
   195  
   196  	benchMap(b, bench{
   197  		setup: func(_ *testing.B, m mapInterface) {
   198  			for i := 0; i < mapSize; i++ {
   199  				m.Store(i, i)
   200  			}
   201  		},
   202  
   203  		perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
   204  			for ; pb.Next(); i++ {
   205  				m.Load(i)
   206  
   207  				if i%mapSize == 0 {
   208  					m.Range(func(k, _ interface{}) bool {
   209  						m.Delete(k)
   210  						return false
   211  					})
   212  					m.Store(i, i)
   213  				}
   214  			}
   215  		},
   216  	})
   217  }