github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/cache/robin_hood_test.go (about) 1 // Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package cache 6 7 import ( 8 "fmt" 9 "io" 10 "runtime" 11 "testing" 12 "time" 13 14 "github.com/cockroachdb/pebble/internal/base" 15 "golang.org/x/exp/rand" 16 ) 17 18 func TestRobinHoodMap(t *testing.T) { 19 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 20 rhMap := newRobinHoodMap(0) 21 defer rhMap.free() 22 23 goMap := make(map[key]*entry) 24 25 randomKey := func() key { 26 n := rng.Intn(len(goMap)) 27 for k := range goMap { 28 if n == 0 { 29 return k 30 } 31 n-- 32 } 33 return key{} 34 } 35 36 ops := 10000 + rng.Intn(10000) 37 for i := 0; i < ops; i++ { 38 var which float64 39 if len(goMap) > 0 { 40 which = rng.Float64() 41 } 42 43 switch { 44 case which < 0.4: 45 // 40% insert. 46 var k key 47 k.id = rng.Uint64() 48 k.fileNum = base.FileNum(rng.Uint64()).DiskFileNum() 49 k.offset = rng.Uint64() 50 e := &entry{} 51 goMap[k] = e 52 rhMap.Put(k, e) 53 if len(goMap) != rhMap.Count() { 54 t.Fatalf("map sizes differ: %d != %d", len(goMap), rhMap.Count()) 55 } 56 57 case which < 0.1: 58 // 10% overwrite. 59 k := randomKey() 60 e := &entry{} 61 goMap[k] = e 62 rhMap.Put(k, e) 63 if len(goMap) != rhMap.Count() { 64 t.Fatalf("map sizes differ: %d != %d", len(goMap), rhMap.Count()) 65 } 66 67 case which < 0.75: 68 // 25% delete. 69 k := randomKey() 70 delete(goMap, k) 71 rhMap.Delete(k) 72 if len(goMap) != rhMap.Count() { 73 t.Fatalf("map sizes differ: %d != %d", len(goMap), rhMap.Count()) 74 } 75 76 default: 77 // 25% lookup. 78 k := randomKey() 79 v := goMap[k] 80 u := rhMap.Get(k) 81 if v != u { 82 t.Fatalf("%s: expected %p, but found %p", k, v, u) 83 } 84 } 85 } 86 87 t.Logf("map size: %d", len(goMap)) 88 } 89 90 const benchSize = 1 << 20 91 92 func BenchmarkGoMapInsert(b *testing.B) { 93 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 94 keys := make([]key, benchSize) 95 for i := range keys { 96 keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum() 97 keys[i].offset = uint64(rng.Intn(1 << 20)) 98 } 99 b.ResetTimer() 100 101 var m map[key]*entry 102 for i, j := 0, 0; i < b.N; i, j = i+1, j+1 { 103 if m == nil || j == len(keys) { 104 b.StopTimer() 105 m = make(map[key]*entry, len(keys)) 106 j = 0 107 b.StartTimer() 108 } 109 m[keys[j]] = nil 110 } 111 } 112 113 func BenchmarkRobinHoodInsert(b *testing.B) { 114 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 115 keys := make([]key, benchSize) 116 for i := range keys { 117 keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum() 118 keys[i].offset = uint64(rng.Intn(1 << 20)) 119 } 120 e := &entry{} 121 b.ResetTimer() 122 123 var m *robinHoodMap 124 for i, j := 0, 0; i < b.N; i, j = i+1, j+1 { 125 if m == nil || j == len(keys) { 126 b.StopTimer() 127 m = newRobinHoodMap(len(keys)) 128 j = 0 129 b.StartTimer() 130 } 131 m.Put(keys[j], e) 132 } 133 134 runtime.KeepAlive(e) 135 } 136 137 func BenchmarkGoMapLookupHit(b *testing.B) { 138 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 139 keys := make([]key, benchSize) 140 m := make(map[key]*entry, len(keys)) 141 e := &entry{} 142 for i := range keys { 143 keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum() 144 keys[i].offset = uint64(rng.Intn(1 << 20)) 145 m[keys[i]] = e 146 } 147 b.ResetTimer() 148 149 var p *entry 150 for i, j := 0, 0; i < b.N; i, j = i+1, j+1 { 151 if j == len(keys) { 152 j = 0 153 } 154 p = m[keys[j]] 155 } 156 157 if testing.Verbose() { 158 fmt.Fprintln(io.Discard, p) 159 } 160 } 161 162 func BenchmarkRobinHoodLookupHit(b *testing.B) { 163 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 164 keys := make([]key, benchSize) 165 m := newRobinHoodMap(len(keys)) 166 e := &entry{} 167 for i := range keys { 168 keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum() 169 keys[i].offset = uint64(rng.Intn(1 << 20)) 170 m.Put(keys[i], e) 171 } 172 b.ResetTimer() 173 174 var p *entry 175 for i, j := 0, 0; i < b.N; i, j = i+1, j+1 { 176 if j == len(keys) { 177 j = 0 178 } 179 p = m.Get(keys[j]) 180 } 181 182 if testing.Verbose() { 183 fmt.Fprintln(io.Discard, p) 184 } 185 runtime.KeepAlive(e) 186 } 187 188 func BenchmarkGoMapLookupMiss(b *testing.B) { 189 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 190 keys := make([]key, benchSize) 191 m := make(map[key]*entry, len(keys)) 192 e := &entry{} 193 for i := range keys { 194 keys[i].id = 1 195 keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum() 196 keys[i].offset = uint64(rng.Intn(1 << 20)) 197 m[keys[i]] = e 198 keys[i].id = 2 199 } 200 b.ResetTimer() 201 202 var p *entry 203 for i, j := 0, 0; i < b.N; i, j = i+1, j+1 { 204 if j == len(keys) { 205 j = 0 206 } 207 p = m[keys[j]] 208 } 209 210 if testing.Verbose() { 211 fmt.Fprintln(io.Discard, p) 212 } 213 } 214 215 func BenchmarkRobinHoodLookupMiss(b *testing.B) { 216 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 217 keys := make([]key, benchSize) 218 m := newRobinHoodMap(len(keys)) 219 e := &entry{} 220 for i := range keys { 221 keys[i].id = 1 222 keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum() 223 keys[i].offset = uint64(rng.Intn(1 << 20)) 224 m.Put(keys[i], e) 225 keys[i].id = 2 226 } 227 b.ResetTimer() 228 229 var p *entry 230 for i, j := 0, 0; i < b.N; i, j = i+1, j+1 { 231 if j == len(keys) { 232 j = 0 233 } 234 p = m.Get(keys[j]) 235 } 236 237 if testing.Verbose() { 238 fmt.Fprintln(io.Discard, p) 239 } 240 runtime.KeepAlive(e) 241 }