github.com/blong14/gache@v0.0.0-20240124023949-89416fd8bbfa/internal/map/tablemap/map_test.go (about) 1 package tablemap_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "strconv" 7 "strings" 8 "sync" 9 "sync/atomic" 10 "testing" 11 "time" 12 13 gtable "github.com/blong14/gache/internal/map/tablemap" 14 ) 15 16 func testGetAndSet(t *testing.T) { 17 t.Parallel() 18 // given 19 tree := gtable.NewWithOptions[[]byte, []byte]( 20 bytes.Compare, 21 gtable.WithCapacity[[]byte, []byte](1024), 22 ) 23 start := time.Now() 24 count := 5_000 25 var wg sync.WaitGroup 26 for i := 0; i < count; i++ { 27 wg.Add(1) 28 go func(idx int) { 29 defer wg.Done() 30 key := []byte(strconv.Itoa(idx)) 31 tree.Set(key, []byte(fmt.Sprintf("value_%d", idx))) 32 }(i) 33 } 34 wg.Wait() 35 for i := 0; i < count; i++ { 36 wg.Add(1) 37 go func(i int) { 38 defer wg.Done() 39 k := []byte(strconv.Itoa(i)) 40 if _, ok := tree.Get(k); !ok { 41 t.Errorf("missing rawKey %d", i) 42 } 43 }(i) 44 } 45 wg.Wait() 46 t.Logf("%s", time.Since(start)) 47 } 48 49 func testRange(t *testing.T) { 50 t.Parallel() 51 // given 52 tree := gtable.New[string, string](strings.Compare) 53 expected := []string{ 54 "key8", 55 "key2", 56 "key", 57 "key5", 58 "key3", 59 "key10", 60 "key7", 61 "key12", 62 "key6", 63 "key9", 64 "key4", 65 "-", 66 } 67 for i, key := range expected { 68 tree.Set(key, fmt.Sprintf("value%d", i)) 69 } 70 71 // when 72 var keys []string 73 tree.Range(func(k, _ string) bool { 74 keys = append(keys, k) 75 return true 76 }) 77 78 // then 79 for _, key := range keys { 80 _, ok := tree.Get(key) 81 if !ok { 82 t.Errorf("%v not found", key) 83 } 84 } 85 } 86 87 func TestTableMap(t *testing.T) { 88 t.Parallel() 89 90 t.Run("get and set", testGetAndSet) 91 t.Run("range", testRange) 92 } 93 94 type bench struct { 95 setup func(*testing.B, *gtable.TableMap[string, string]) 96 perG func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string]) 97 teardown func(*testing.B, *gtable.TableMap[string, string]) func() 98 } 99 100 func newMap() *gtable.TableMap[string, string] { 101 return gtable.New[string, string](strings.Compare) 102 } 103 104 func benchMap(b *testing.B, bench bench) { 105 b.Run("tablemap benchmark", func(b *testing.B) { 106 m := newMap() 107 if bench.setup != nil { 108 bench.setup(b, m) 109 } 110 b.ReportAllocs() 111 b.ResetTimer() 112 var i int64 113 b.RunParallel(func(pb *testing.PB) { 114 id := int(atomic.AddInt64(&i, 1) - 1) 115 bench.perG(b, pb, id*b.N, m) 116 }) 117 if bench.teardown != nil { 118 b.Cleanup(bench.teardown(b, m)) 119 } 120 }) 121 } 122 123 func BenchmarkConcurrent_LoadMostlyHits(b *testing.B) { 124 const hits, misses = 1023, 1 125 126 benchMap(b, bench{ 127 setup: func(_ *testing.B, m *gtable.TableMap[string, string]) { 128 for i := 0; i < hits; i++ { 129 m.Set(strconv.Itoa(i), strconv.Itoa(i)) 130 } 131 // Prime the map to get it into a steady state. 132 for i := 0; i < hits*2; i++ { 133 m.Range(func(_, _ string) bool { return true }) 134 } 135 }, 136 perG: func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string]) { 137 for ; pb.Next(); i++ { 138 m.Get(strconv.Itoa(i % (hits + misses))) 139 } 140 }, 141 }) 142 143 } 144 145 func BenchmarkConcurrent_LoadOrStoreBalanced(b *testing.B) { 146 const hits, misses = 1023, 1023 147 148 benchMap(b, bench{ 149 setup: func(b *testing.B, m *gtable.TableMap[string, string]) { 150 for i := 0; i < hits; i++ { 151 m.Set(strconv.Itoa(i), strconv.Itoa(i)) 152 } 153 // Prime the map to get it into a steady state. 154 for i := 0; i < hits*2; i++ { 155 m.Range(func(_, _ string) bool { return true }) 156 } 157 }, 158 perG: func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string]) { 159 for ; pb.Next(); i++ { 160 j := i % (hits + misses) 161 if j < hits { 162 if _, ok := m.Get(strconv.Itoa(j)); !ok { 163 b.Fatalf("unexpected miss for key %v", j) 164 } 165 } else { 166 m.Set(strconv.Itoa(j), strconv.Itoa(j)) 167 } 168 } 169 }, 170 }) 171 } 172 173 func BenchmarkConcurrent_LoadOrStoreCollision(b *testing.B) { 174 benchMap(b, bench{ 175 perG: func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string]) { 176 for ; pb.Next(); i++ { 177 m.Set("key", "value") 178 } 179 }, 180 }) 181 } 182 183 func BenchmarkConcurrent_Range(b *testing.B) { 184 const mapSize = 1 << 10 185 186 benchMap(b, bench{ 187 setup: func(_ *testing.B, m *gtable.TableMap[string, string]) { 188 for i := 0; i < mapSize; i++ { 189 m.Set(strconv.Itoa(i), strconv.Itoa(i)) 190 } 191 }, 192 perG: func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string]) { 193 for ; pb.Next(); i++ { 194 m.Range(func(_, _ string) bool { return true }) 195 } 196 }, 197 }) 198 }