rss-tools/vendor/go.etcd.io/bbolt/internal/freelist/hashmap.go (view raw)
| 1 | package freelist |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "reflect" |
| 6 | "sort" |
| 7 | |
| 8 | "go.etcd.io/bbolt/internal/common" |
| 9 | ) |
| 10 | |
| 11 | // pidSet holds the set of starting pgids which have the same span size |
| 12 | type pidSet map[common.Pgid]struct{} |
| 13 | |
| 14 | type hashMap struct { |
| 15 | *shared |
| 16 | |
| 17 | freePagesCount uint64 // count of free pages(hashmap version) |
| 18 | freemaps map[uint64]pidSet // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size |
| 19 | forwardMap map[common.Pgid]uint64 // key is start pgid, value is its span size |
| 20 | backwardMap map[common.Pgid]uint64 // key is end pgid, value is its span size |
| 21 | } |
| 22 | |
| 23 | func (f *hashMap) Init(pgids common.Pgids) { |
| 24 | // reset the counter when freelist init |
| 25 | f.freePagesCount = 0 |
| 26 | f.freemaps = make(map[uint64]pidSet) |
| 27 | f.forwardMap = make(map[common.Pgid]uint64) |
| 28 | f.backwardMap = make(map[common.Pgid]uint64) |
| 29 | |
| 30 | if len(pgids) == 0 { |
| 31 | return |
| 32 | } |
| 33 | |
| 34 | if !sort.SliceIsSorted([]common.Pgid(pgids), func(i, j int) bool { return pgids[i] < pgids[j] }) { |
| 35 | panic("pgids not sorted") |
| 36 | } |
| 37 | |
| 38 | size := uint64(1) |
| 39 | start := pgids[0] |
| 40 | |
| 41 | for i := 1; i < len(pgids); i++ { |
| 42 | // continuous page |
| 43 | if pgids[i] == pgids[i-1]+1 { |
| 44 | size++ |
| 45 | } else { |
| 46 | f.addSpan(start, size) |
| 47 | |
| 48 | size = 1 |
| 49 | start = pgids[i] |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | // init the tail |
| 54 | if size != 0 && start != 0 { |
| 55 | f.addSpan(start, size) |
| 56 | } |
| 57 | |
| 58 | f.reindex() |
| 59 | } |
| 60 | |
| 61 | func (f *hashMap) Allocate(txid common.Txid, n int) common.Pgid { |
| 62 | if n == 0 { |
| 63 | return 0 |
| 64 | } |
| 65 | |
| 66 | // if we have a exact size match just return short path |
| 67 | if bm, ok := f.freemaps[uint64(n)]; ok { |
| 68 | for pid := range bm { |
| 69 | // remove the span |
| 70 | f.delSpan(pid, uint64(n)) |
| 71 | |
| 72 | f.allocs[pid] = txid |
| 73 | |
| 74 | for i := common.Pgid(0); i < common.Pgid(n); i++ { |
| 75 | delete(f.cache, pid+i) |
| 76 | } |
| 77 | return pid |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | // lookup the map to find larger span |
| 82 | for size, bm := range f.freemaps { |
| 83 | if size < uint64(n) { |
| 84 | continue |
| 85 | } |
| 86 | |
| 87 | for pid := range bm { |
| 88 | // remove the initial |
| 89 | f.delSpan(pid, size) |
| 90 | |
| 91 | f.allocs[pid] = txid |
| 92 | |
| 93 | remain := size - uint64(n) |
| 94 | |
| 95 | // add remain span |
| 96 | f.addSpan(pid+common.Pgid(n), remain) |
| 97 | |
| 98 | for i := common.Pgid(0); i < common.Pgid(n); i++ { |
| 99 | delete(f.cache, pid+i) |
| 100 | } |
| 101 | return pid |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | return 0 |
| 106 | } |
| 107 | |
| 108 | func (f *hashMap) FreeCount() int { |
| 109 | common.Verify(func() { |
| 110 | expectedFreePageCount := f.hashmapFreeCountSlow() |
| 111 | common.Assert(int(f.freePagesCount) == expectedFreePageCount, |
| 112 | "freePagesCount (%d) is out of sync with free pages map (%d)", f.freePagesCount, expectedFreePageCount) |
| 113 | }) |
| 114 | return int(f.freePagesCount) |
| 115 | } |
| 116 | |
| 117 | func (f *hashMap) freePageIds() common.Pgids { |
| 118 | count := f.FreeCount() |
| 119 | if count == 0 { |
| 120 | return common.Pgids{} |
| 121 | } |
| 122 | |
| 123 | m := make([]common.Pgid, 0, count) |
| 124 | |
| 125 | startPageIds := make([]common.Pgid, 0, len(f.forwardMap)) |
| 126 | for k := range f.forwardMap { |
| 127 | startPageIds = append(startPageIds, k) |
| 128 | } |
| 129 | sort.Sort(common.Pgids(startPageIds)) |
| 130 | |
| 131 | for _, start := range startPageIds { |
| 132 | if size, ok := f.forwardMap[start]; ok { |
| 133 | for i := 0; i < int(size); i++ { |
| 134 | m = append(m, start+common.Pgid(i)) |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | return m |
| 140 | } |
| 141 | |
| 142 | func (f *hashMap) hashmapFreeCountSlow() int { |
| 143 | count := 0 |
| 144 | for _, size := range f.forwardMap { |
| 145 | count += int(size) |
| 146 | } |
| 147 | return count |
| 148 | } |
| 149 | |
| 150 | func (f *hashMap) addSpan(start common.Pgid, size uint64) { |
| 151 | f.backwardMap[start-1+common.Pgid(size)] = size |
| 152 | f.forwardMap[start] = size |
| 153 | if _, ok := f.freemaps[size]; !ok { |
| 154 | f.freemaps[size] = make(map[common.Pgid]struct{}) |
| 155 | } |
| 156 | |
| 157 | f.freemaps[size][start] = struct{}{} |
| 158 | f.freePagesCount += size |
| 159 | } |
| 160 | |
| 161 | func (f *hashMap) delSpan(start common.Pgid, size uint64) { |
| 162 | delete(f.forwardMap, start) |
| 163 | delete(f.backwardMap, start+common.Pgid(size-1)) |
| 164 | delete(f.freemaps[size], start) |
| 165 | if len(f.freemaps[size]) == 0 { |
| 166 | delete(f.freemaps, size) |
| 167 | } |
| 168 | f.freePagesCount -= size |
| 169 | } |
| 170 | |
| 171 | func (f *hashMap) mergeSpans(ids common.Pgids) { |
| 172 | common.Verify(func() { |
| 173 | ids1Freemap := f.idsFromFreemaps() |
| 174 | ids2Forward := f.idsFromForwardMap() |
| 175 | ids3Backward := f.idsFromBackwardMap() |
| 176 | |
| 177 | if !reflect.DeepEqual(ids1Freemap, ids2Forward) { |
| 178 | panic(fmt.Sprintf("Detected mismatch, f.freemaps: %v, f.forwardMap: %v", f.freemaps, f.forwardMap)) |
| 179 | } |
| 180 | if !reflect.DeepEqual(ids1Freemap, ids3Backward) { |
| 181 | panic(fmt.Sprintf("Detected mismatch, f.freemaps: %v, f.backwardMap: %v", f.freemaps, f.backwardMap)) |
| 182 | } |
| 183 | |
| 184 | sort.Sort(ids) |
| 185 | prev := common.Pgid(0) |
| 186 | for _, id := range ids { |
| 187 | // The ids shouldn't have duplicated free ID. |
| 188 | if prev == id { |
| 189 | panic(fmt.Sprintf("detected duplicated free ID: %d in ids: %v", id, ids)) |
| 190 | } |
| 191 | prev = id |
| 192 | |
| 193 | // The ids shouldn't have any overlap with the existing f.freemaps. |
| 194 | if _, ok := ids1Freemap[id]; ok { |
| 195 | panic(fmt.Sprintf("detected overlapped free page ID: %d between ids: %v and existing f.freemaps: %v", id, ids, f.freemaps)) |
| 196 | } |
| 197 | } |
| 198 | }) |
| 199 | for _, id := range ids { |
| 200 | // try to see if we can merge and update |
| 201 | f.mergeWithExistingSpan(id) |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | // mergeWithExistingSpan merges pid to the existing free spans, try to merge it backward and forward |
| 206 | func (f *hashMap) mergeWithExistingSpan(pid common.Pgid) { |
| 207 | prev := pid - 1 |
| 208 | next := pid + 1 |
| 209 | |
| 210 | preSize, mergeWithPrev := f.backwardMap[prev] |
| 211 | nextSize, mergeWithNext := f.forwardMap[next] |
| 212 | newStart := pid |
| 213 | newSize := uint64(1) |
| 214 | |
| 215 | if mergeWithPrev { |
| 216 | //merge with previous span |
| 217 | start := prev + 1 - common.Pgid(preSize) |
| 218 | f.delSpan(start, preSize) |
| 219 | |
| 220 | newStart -= common.Pgid(preSize) |
| 221 | newSize += preSize |
| 222 | } |
| 223 | |
| 224 | if mergeWithNext { |
| 225 | // merge with next span |
| 226 | f.delSpan(next, nextSize) |
| 227 | newSize += nextSize |
| 228 | } |
| 229 | |
| 230 | f.addSpan(newStart, newSize) |
| 231 | } |
| 232 | |
| 233 | // idsFromFreemaps get all free page IDs from f.freemaps. |
| 234 | // used by test only. |
| 235 | func (f *hashMap) idsFromFreemaps() map[common.Pgid]struct{} { |
| 236 | ids := make(map[common.Pgid]struct{}) |
| 237 | for size, idSet := range f.freemaps { |
| 238 | for start := range idSet { |
| 239 | for i := 0; i < int(size); i++ { |
| 240 | id := start + common.Pgid(i) |
| 241 | if _, ok := ids[id]; ok { |
| 242 | panic(fmt.Sprintf("detected duplicated free page ID: %d in f.freemaps: %v", id, f.freemaps)) |
| 243 | } |
| 244 | ids[id] = struct{}{} |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | return ids |
| 249 | } |
| 250 | |
| 251 | // idsFromForwardMap get all free page IDs from f.forwardMap. |
| 252 | // used by test only. |
| 253 | func (f *hashMap) idsFromForwardMap() map[common.Pgid]struct{} { |
| 254 | ids := make(map[common.Pgid]struct{}) |
| 255 | for start, size := range f.forwardMap { |
| 256 | for i := 0; i < int(size); i++ { |
| 257 | id := start + common.Pgid(i) |
| 258 | if _, ok := ids[id]; ok { |
| 259 | panic(fmt.Sprintf("detected duplicated free page ID: %d in f.forwardMap: %v", id, f.forwardMap)) |
| 260 | } |
| 261 | ids[id] = struct{}{} |
| 262 | } |
| 263 | } |
| 264 | return ids |
| 265 | } |
| 266 | |
| 267 | // idsFromBackwardMap get all free page IDs from f.backwardMap. |
| 268 | // used by test only. |
| 269 | func (f *hashMap) idsFromBackwardMap() map[common.Pgid]struct{} { |
| 270 | ids := make(map[common.Pgid]struct{}) |
| 271 | for end, size := range f.backwardMap { |
| 272 | for i := 0; i < int(size); i++ { |
| 273 | id := end - common.Pgid(i) |
| 274 | if _, ok := ids[id]; ok { |
| 275 | panic(fmt.Sprintf("detected duplicated free page ID: %d in f.backwardMap: %v", id, f.backwardMap)) |
| 276 | } |
| 277 | ids[id] = struct{}{} |
| 278 | } |
| 279 | } |
| 280 | return ids |
| 281 | } |
| 282 | |
| 283 | func NewHashMapFreelist() Interface { |
| 284 | hm := &hashMap{ |
| 285 | shared: newShared(), |
| 286 | freemaps: make(map[uint64]pidSet), |
| 287 | forwardMap: make(map[common.Pgid]uint64), |
| 288 | backwardMap: make(map[common.Pgid]uint64), |
| 289 | } |
| 290 | hm.Interface = hm |
| 291 | return hm |
| 292 | } |