github.com/tacshi/go-ethereum@v0.0.0-20230616113857-84a434e20921/metrics/chunked_associative_array.go (about) 1 package metrics 2 3 // Ported from 4 // https://github.com/dropwizard/metrics/blob/release/4.2.x/metrics-core/src/main/java/com/codahale/metrics/ChunkedAssociativeLongArray.java 5 6 import ( 7 "sort" 8 "strconv" 9 "strings" 10 11 "github.com/gammazero/deque" 12 ) 13 14 const ( 15 ChunkedAssociativeArrayDefaultChunkSize = 512 16 ChunkedAssociativeArrayMaxCacheSize = 128 17 ) 18 19 type ChunkedAssociativeArray struct { 20 defaultChunkSize int 21 22 /* 23 * We use this ArrayDeque as cache to store chunks that are expired and removed from main data structure. 24 * Then instead of allocating new AssociativeArrayChunk immediately we are trying to poll one from this deque. 25 * So if you have constant or slowly changing load ChunkedAssociativeLongArray will never 26 * throw away old chunks or allocate new ones which makes this data structure almost garbage free. 27 */ 28 chunksCache *deque.Deque[*AssociativeArrayChunk] 29 chunks *deque.Deque[*AssociativeArrayChunk] 30 } 31 32 func NewChunkedAssociativeArray(chunkSize int) *ChunkedAssociativeArray { 33 return &ChunkedAssociativeArray{ 34 defaultChunkSize: chunkSize, 35 chunksCache: deque.New[*AssociativeArrayChunk](ChunkedAssociativeArrayMaxCacheSize, ChunkedAssociativeArrayMaxCacheSize), 36 chunks: deque.New[*AssociativeArrayChunk](), 37 } 38 } 39 40 func (caa *ChunkedAssociativeArray) Clear() { 41 for i := 0; i < caa.chunks.Len(); i++ { 42 chunk := caa.chunks.PopBack() 43 caa.freeChunk(chunk) 44 } 45 } 46 47 func (caa *ChunkedAssociativeArray) AllocateChunk() *AssociativeArrayChunk { 48 if caa.chunksCache.Len() == 0 { 49 return NewAssociativeArrayChunk(caa.defaultChunkSize) 50 } 51 52 chunk := caa.chunksCache.PopBack() 53 chunk.cursor = 0 54 chunk.startIndex = 0 55 chunk.chunkSize = len(chunk.keys) 56 57 return chunk 58 } 59 60 func (caa *ChunkedAssociativeArray) freeChunk(chunk *AssociativeArrayChunk) { 61 if caa.chunksCache.Len() < ChunkedAssociativeArrayMaxCacheSize { 62 caa.chunksCache.PushBack(chunk) 63 } 64 } 65 66 func (caa *ChunkedAssociativeArray) Put(key int64, value int64) { 67 var activeChunk *AssociativeArrayChunk 68 if caa.chunks.Len() > 0 { 69 activeChunk = caa.chunks.Back() 70 } 71 72 if activeChunk != nil && activeChunk.cursor != 0 && activeChunk.keys[activeChunk.cursor-1] > key { 73 // Key must be the same as last inserted or bigger 74 key = activeChunk.keys[activeChunk.cursor-1] + 1 75 } 76 if activeChunk == nil || activeChunk.cursor-activeChunk.startIndex == activeChunk.chunkSize { 77 // The last chunk doesn't exist or full 78 activeChunk = caa.AllocateChunk() 79 caa.chunks.PushBack(activeChunk) 80 } 81 activeChunk.Append(key, value) 82 } 83 84 func (caa *ChunkedAssociativeArray) Values() []int64 { 85 valuesSize := caa.Size() 86 if valuesSize == 0 { 87 // Empty 88 return []int64{0} 89 } 90 91 values := make([]int64, 0, valuesSize) 92 caa.chunks.Index(func(chunk *AssociativeArrayChunk) bool { 93 values = append(values, chunk.values[chunk.startIndex:chunk.cursor]...) 94 return false 95 }) 96 97 return values 98 } 99 100 func (caa *ChunkedAssociativeArray) Size() int { 101 var result int 102 caa.chunks.Index(func(chunk *AssociativeArrayChunk) bool { 103 result += chunk.cursor - chunk.startIndex 104 return false 105 }) 106 return result 107 } 108 109 func (caa *ChunkedAssociativeArray) String() string { 110 var builder strings.Builder 111 first := true 112 caa.chunks.Index(func(chunk *AssociativeArrayChunk) bool { 113 if first { 114 first = false 115 } else { 116 builder.WriteString("->") 117 } 118 builder.WriteString("[") 119 for i := chunk.startIndex; i < chunk.cursor; i++ { 120 builder.WriteString("(") 121 builder.WriteString(strconv.FormatInt(chunk.keys[i], 10)) 122 builder.WriteString(": ") 123 builder.WriteString(strconv.FormatInt(chunk.values[i], 10)) 124 builder.WriteString(") ") 125 } 126 builder.WriteString("]") 127 return false 128 }) 129 130 return builder.String() 131 } 132 133 // Trim tries to trim all beyond specified boundaries 134 // startKey: the start value for which all elements less than it should be removed. 135 // endKey: the end value for which all elements greater/equals than it should be removed 136 func (caa *ChunkedAssociativeArray) Trim(startKey int64, endKey int64) { 137 /* 138 * [3, 4, 5, 9] -> [10, 13, 14, 15] -> [21, 24, 29, 30] -> [31] :: start layout 139 * |5______________________________23| :: trim(5, 23) 140 * [5, 9] -> [10, 13, 14, 15] -> [21] :: result layout 141 */ 142 // Remove elements that are too large 143 indexBeforeEndKey := caa.chunks.RIndex(func(chunk *AssociativeArrayChunk) bool { 144 if chunk.IsFirstElementEmptyOrGreaterEqualThanKey(endKey) { 145 return false 146 } 147 148 chunk.cursor = chunk.FindFirstIndexOfGreaterEqualElements(endKey) 149 return true 150 }) 151 152 // Remove chunks that only contain elements that are too large 153 if indexBeforeEndKey >= 0 { 154 for i := caa.chunks.Len() - 1; i > indexBeforeEndKey; i-- { 155 chunk := caa.chunks.PopBack() 156 caa.freeChunk(chunk) 157 } 158 } 159 160 // Remove elements that are too small 161 indexAfterStartKey := caa.chunks.Index(func(chunk *AssociativeArrayChunk) bool { 162 if chunk.IsLastElementEmptyOrLessThanKey(startKey) { 163 return false 164 } 165 166 newStartIndex := chunk.FindFirstIndexOfGreaterEqualElements(startKey) 167 if chunk.startIndex != newStartIndex { 168 chunk.startIndex = newStartIndex 169 chunk.chunkSize = chunk.cursor - chunk.startIndex 170 } 171 return true 172 }) 173 174 // Remove chunks that only contain elements that are too small 175 for i := 0; i < indexAfterStartKey; i++ { 176 chunk := caa.chunks.PopFront() 177 caa.freeChunk(chunk) 178 } 179 } 180 181 type AssociativeArrayChunk struct { 182 keys []int64 183 values []int64 184 185 chunkSize int 186 startIndex int 187 cursor int 188 } 189 190 func NewAssociativeArrayChunk(chunkSize int) *AssociativeArrayChunk { 191 return &AssociativeArrayChunk{ 192 keys: make([]int64, chunkSize), 193 values: make([]int64, chunkSize), 194 chunkSize: chunkSize, 195 startIndex: 0, 196 cursor: 0, 197 } 198 } 199 200 func (c *AssociativeArrayChunk) Append(key int64, value int64) { 201 c.keys[c.cursor] = key 202 c.values[c.cursor] = value 203 c.cursor++ 204 } 205 206 func (c *AssociativeArrayChunk) IsFirstElementEmptyOrGreaterEqualThanKey(key int64) bool { 207 return c.cursor == c.startIndex || c.keys[c.startIndex] >= key 208 } 209 210 func (c *AssociativeArrayChunk) IsLastElementEmptyOrLessThanKey(key int64) bool { 211 return c.cursor == c.startIndex || c.keys[c.cursor-1] < key 212 } 213 214 func (c *AssociativeArrayChunk) FindFirstIndexOfGreaterEqualElements(minKey int64) int { 215 if c.cursor == c.startIndex || c.keys[c.startIndex] >= minKey { 216 return c.startIndex 217 } 218 elements := c.keys[c.startIndex:c.cursor] 219 keyIndex := sort.Search(len(elements), func(i int) bool { return elements[i] >= minKey }) 220 221 return c.startIndex + keyIndex 222 }