github.com/blong14/gache@v0.0.0-20240124023949-89416fd8bbfa/internal/map/skiplist/map_test.go (about)

     1  package skiplist_test
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"sync"
     7  	"sync/atomic"
     8  	"testing"
     9  	"time"
    10  
    11  	gskl "github.com/blong14/gache/internal/map/skiplist"
    12  )
    13  
    14  type test struct {
    15  	setup    func(*testing.T, *gskl.SkipList)
    16  	run      func(t *testing.T, m *gskl.SkipList)
    17  	teardown func(*testing.T, *gskl.SkipList) func()
    18  }
    19  
    20  func testMap(t *testing.T, name string, test test) {
    21  	t.Run(fmt.Sprintf("skip list test %s", name), func(t *testing.T) {
    22  		t.Parallel()
    23  		m := gskl.New()
    24  		if test.setup != nil {
    25  			test.setup(t, m)
    26  		}
    27  		test.run(t, m)
    28  		if test.teardown != nil {
    29  			t.Cleanup(func() {
    30  				test.teardown(t, m)
    31  			})
    32  		}
    33  	})
    34  }
    35  
    36  func TestHeight(t *testing.T) {
    37  	expected := 20
    38  	testMap(t, "height", test{
    39  		setup: func(t *testing.T, m *gskl.SkipList) {
    40  			for i := 0; i < expected; i++ {
    41  				err := m.Set(
    42  					[]byte(fmt.Sprintf("key_%d", i)), []byte(fmt.Sprintf("value__%d", i)))
    43  				if err != nil {
    44  					t.Fail()
    45  				}
    46  			}
    47  		},
    48  		run: func(t *testing.T, m *gskl.SkipList) {
    49  			actual := m.Height()
    50  			if actual > uint64(expected) {
    51  				t.Errorf("w %d g %d", expected, actual)
    52  			}
    53  		},
    54  	})
    55  }
    56  
    57  func TestCount(t *testing.T) {
    58  	expected := 100
    59  	testMap(t, "count", test{
    60  		setup: func(t *testing.T, m *gskl.SkipList) {
    61  			for i := 0; i < expected; i++ {
    62  				err := m.Set(
    63  					[]byte(fmt.Sprintf("key_%d", i)), []byte(fmt.Sprintf("value__%d", i)))
    64  				if err != nil {
    65  					t.Fail()
    66  				}
    67  			}
    68  		},
    69  		run: func(t *testing.T, m *gskl.SkipList) {
    70  			actual := m.Count()
    71  			if actual != uint64(expected) {
    72  				t.Errorf("w %d g %d", expected, actual)
    73  			}
    74  		},
    75  	})
    76  }
    77  
    78  func TestGetAndSet(t *testing.T) {
    79  	count := 50_000
    80  	testMap(t, "get and set", test{
    81  		run: func(t *testing.T, m *gskl.SkipList) {
    82  			start := time.Now()
    83  			var wg sync.WaitGroup
    84  			for i := 0; i < count; i++ {
    85  				wg.Add(1)
    86  				go func(indx int) {
    87  					defer wg.Done()
    88  					k := []byte(fmt.Sprintf("key-%d", indx))
    89  					err := m.Set(k, []byte(fmt.Sprintf("value__%d", indx)))
    90  					if err != nil {
    91  						t.Error(err)
    92  					}
    93  				}(i)
    94  			}
    95  			wg.Wait()
    96  			t.Logf("%s", time.Since(start))
    97  			for i := 0; i < count; i++ {
    98  				wg.Add(1)
    99  				go func(idx int) {
   100  					defer wg.Done()
   101  					k := []byte(fmt.Sprintf("key-%d", idx))
   102  					if _, ok := m.Get(k); !ok {
   103  						t.Errorf("missing rawKey key-%d", idx)
   104  					}
   105  				}(i)
   106  			}
   107  			wg.Wait()
   108  			// m.Print()
   109  			t.Logf("%s", time.Since(start))
   110  		},
   111  	})
   112  }
   113  
   114  type bench struct {
   115  	setup    func(*testing.B, *gskl.SkipList)
   116  	perG     func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList)
   117  	teardown func(*testing.B, *gskl.SkipList) func()
   118  }
   119  
   120  func benchMap(b *testing.B, bench bench) {
   121  	b.Run("skip list benchmark", func(b *testing.B) {
   122  		m := gskl.New()
   123  		if bench.setup != nil {
   124  			bench.setup(b, m)
   125  		}
   126  		b.ReportAllocs()
   127  		b.ResetTimer()
   128  		var i int64
   129  		b.RunParallel(func(pb *testing.PB) {
   130  			id := int(atomic.AddInt64(&i, 1) - 1)
   131  			bench.perG(b, pb, id*b.N, m)
   132  		})
   133  		if bench.teardown != nil {
   134  			b.Cleanup(func() {
   135  				bench.teardown(b, m)
   136  			})
   137  		}
   138  	})
   139  }
   140  
   141  func BenchmarkSkiplist_LoadMostlyHits(b *testing.B) {
   142  	const hits, misses = 1023, 1
   143  
   144  	benchMap(b, bench{
   145  		setup: func(b *testing.B, m *gskl.SkipList) {
   146  			b.StopTimer()
   147  			for i := 0; i < hits; i++ {
   148  				v := strconv.Itoa(i)
   149  				err := m.Set([]byte(v), []byte(v))
   150  				if err != nil {
   151  					b.Fail()
   152  				}
   153  			}
   154  		},
   155  		perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) {
   156  			b.StartTimer()
   157  			for ; pb.Next(); i++ {
   158  				m.Get([]byte(strconv.Itoa(i % (hits + misses))))
   159  			}
   160  		},
   161  	})
   162  }
   163  
   164  func BenchmarkSkiplist_XLoadMostlyHits(b *testing.B) {
   165  	const hits, misses = 1023, 1
   166  	mmap := make(map[string]string)
   167  	var mtx sync.RWMutex
   168  	benchMap(b, bench{
   169  		setup: func(b *testing.B, m *gskl.SkipList) {
   170  			mtx.Lock()
   171  			for i := 0; i < hits; i++ {
   172  				key := strconv.Itoa(i)
   173  				mmap[key] = key
   174  			}
   175  			mtx.Unlock()
   176  		},
   177  		perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) {
   178  			for ; pb.Next(); i++ {
   179  				k := strconv.Itoa(i % (hits + misses))
   180  				mtx.RLock()
   181  				_ = mmap[k]
   182  				mtx.RUnlock()
   183  			}
   184  		},
   185  	})
   186  }
   187  
   188  func BenchmarkSkiplist_LoadMostlyMisses(b *testing.B) {
   189  	const hits, misses = 1, 1023
   190  	benchMap(b, bench{
   191  		setup: func(_ *testing.B, m *gskl.SkipList) {
   192  			for i := 0; i < hits; i++ {
   193  				key := []byte(strconv.Itoa(i))
   194  				if err := m.Set(key, key); err != nil {
   195  					b.Fail()
   196  				}
   197  			}
   198  		},
   199  		perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) {
   200  			for ; pb.Next(); i++ {
   201  				m.Get([]byte(strconv.Itoa(i % (hits + misses))))
   202  			}
   203  		},
   204  	})
   205  }
   206  
   207  func BenchmarkSkiplist_LoadOrStoreBalanced(b *testing.B) {
   208  	const hits, misses = 1023, 1023
   209  	value := []byte("value")
   210  	benchMap(b, bench{
   211  		setup: func(b *testing.B, m *gskl.SkipList) {
   212  			for i := 0; i < hits; i++ {
   213  				key := []byte(strconv.Itoa(i))
   214  				if err := m.Set(key, value); err != nil {
   215  					b.Fail()
   216  				}
   217  			}
   218  		},
   219  		perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) {
   220  			for ; pb.Next(); i++ {
   221  				j := i % (hits + misses)
   222  				key := []byte(strconv.Itoa(j))
   223  				if j < hits {
   224  					if _, ok := m.Get(key); !ok {
   225  						b.Fatalf("unexpected miss for %v", j)
   226  					}
   227  				} else {
   228  					if err := m.Set(key, value); err != nil {
   229  						b.Error(err)
   230  					}
   231  				}
   232  			}
   233  		},
   234  	})
   235  }
   236  
   237  func BenchmarkSkiplist_LoadOrStoreUnique(b *testing.B) {
   238  	const hits = 1023
   239  	value := []byte("value")
   240  	benchMap(b, bench{
   241  		perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) {
   242  			for ; pb.Next(); i++ {
   243  				j := i % hits
   244  				key := []byte(strconv.Itoa(j))
   245  				if _, ok := m.Get(key); !ok {
   246  					if err := m.Set(key, value); err != nil {
   247  						b.Error(err)
   248  					}
   249  				}
   250  			}
   251  		},
   252  	})
   253  }
   254  
   255  func BenchmarkSkiplist_LoadOrStoreCollision(b *testing.B) {
   256  	const hits = 1023
   257  	value := []byte("value")
   258  	benchMap(b, bench{
   259  		setup: func(b *testing.B, m *gskl.SkipList) {
   260  			for i := 0; i < hits; i++ {
   261  				key := []byte(strconv.Itoa(i))
   262  				if err := m.Set(key, value); err != nil {
   263  					b.Fail()
   264  				}
   265  			}
   266  		},
   267  		perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) {
   268  			for ; pb.Next(); i++ {
   269  				j := i % hits
   270  				key := []byte(strconv.Itoa(j))
   271  				if _, ok := m.Get(key); ok {
   272  					if err := m.Set(key, value); err != nil {
   273  						b.Error(err)
   274  					}
   275  				} else {
   276  					b.Errorf("unexpected miss %s", key)
   277  				}
   278  			}
   279  		},
   280  	})
   281  }
   282  
   283  func BenchmarkSkiplist_AdversarialAlloc(b *testing.B) {
   284  	value := []byte("value")
   285  	benchMap(b, bench{
   286  		perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) {
   287  			var stores, loadsSinceStore int64
   288  			for ; pb.Next(); i++ {
   289  				key := []byte(strconv.Itoa(i))
   290  				m.Get(key)
   291  				if loadsSinceStore++; loadsSinceStore > stores {
   292  					if _, ok := m.Get(key); !ok {
   293  						err := m.Set(key, value)
   294  						if err != nil {
   295  							b.Error(err)
   296  						}
   297  					}
   298  					loadsSinceStore = 0
   299  					stores++
   300  				}
   301  			}
   302  		},
   303  	})
   304  }
   305  
   306  func BenchmarkSkiplist_Range(b *testing.B) {
   307  	const mapSize = 1 << 10
   308  	value := []byte("")
   309  	benchMap(b, bench{
   310  		setup: func(_ *testing.B, m *gskl.SkipList) {
   311  			for i := 0; i < mapSize; i++ {
   312  				key := []byte(strconv.Itoa(i))
   313  				err := m.Set(key, value)
   314  				if err != nil {
   315  					b.Fail()
   316  				}
   317  			}
   318  		},
   319  		perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) {
   320  			for ; pb.Next(); i++ {
   321  				m.Range(func(_, _ []byte) bool { return true })
   322  			}
   323  		},
   324  	})
   325  }