github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/container/queue/chunkqueue.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package queue 15 16 import ( 17 "fmt" 18 "sync" 19 "unsafe" 20 ) 21 22 const ( 23 // defaultSizePerChunk set the default size of each chunk be 1024 bytes (1kB) 24 defaultSizePerChunk = 1024 25 // minimumChunkLen is the minimum length of each chunk 26 minimumChunkLen = 16 27 // defaultPitchArrayLen is the default length of the chunk pointers array 28 defaultPitchArrayLen = 16 29 ) 30 31 // ChunkQueue is a generic, efficient, iterable and GC-friendly queue. 32 // Attention, it's not thread-safe. 33 type ChunkQueue[T any] struct { 34 // [head, tail) is the section of chunks in use 35 head int 36 tail int 37 38 // size is number of elements in queue 39 size int 40 41 // chunks is an array to store chunk pointers 42 chunks []*chunk[T] 43 // chunkLength is the max number of elements stored in every chunk. 44 chunkLength int 45 chunkPool sync.Pool 46 defaultValue T 47 } 48 49 func (q *ChunkQueue[T]) firstChunk() *chunk[T] { 50 return q.chunks[q.head] 51 } 52 53 func (q *ChunkQueue[T]) lastChunk() *chunk[T] { 54 return q.chunks[q.tail-1] 55 } 56 57 // NewChunkQueue creates a new ChunkQueue 58 func NewChunkQueue[T any]() *ChunkQueue[T] { 59 return NewChunkQueueLeastCapacity[T](1) 60 } 61 62 // NewChunkQueueLeastCapacity creates a ChunkQueue with an argument minCapacity. 63 // It requests that the queue capacity be at least minCapacity. And it's similar 64 // to the cap argument when making a slice using make([]T, len, cap) 65 func NewChunkQueueLeastCapacity[T any](minCapacity int) *ChunkQueue[T] { 66 elementSize := unsafe.Sizeof(*new(T)) 67 if elementSize == 0 { 68 // To avoid divided by zero 69 elementSize = 1 70 } 71 72 chunkLength := int(defaultSizePerChunk / elementSize) 73 if chunkLength < minimumChunkLen { 74 chunkLength = minimumChunkLen 75 } 76 77 q := &ChunkQueue[T]{ 78 head: 0, 79 tail: 0, 80 size: 0, 81 chunkLength: chunkLength, 82 } 83 q.chunkPool = sync.Pool{ 84 New: func() any { 85 return newChunk[T](q.chunkLength, q) 86 }, 87 } 88 89 q.chunks = make([]*chunk[T], defaultPitchArrayLen) 90 q.addSpace(minCapacity) 91 return q 92 } 93 94 // Len returns the number of elements in queue 95 func (q *ChunkQueue[T]) Len() int { 96 return q.size 97 } 98 99 // Cap returns the capacity of the queue. The queue can hold more elements 100 // than that number by automatic expansion 101 func (q *ChunkQueue[T]) Cap() int { 102 return q.chunkLength*(q.tail-q.head) - q.chunks[q.head].l 103 } 104 105 // Empty indicates whether the queue is empty 106 func (q *ChunkQueue[T]) Empty() bool { 107 return q.size == 0 108 } 109 110 // Peek returns the value of a given index. It does NOT support modifying the value 111 func (q *ChunkQueue[T]) Peek(idx int) T { 112 if idx < 0 || idx >= q.size { 113 panic(fmt.Sprintf("[ChunkQueue]: index %d os out of index [0, %d)", idx, q.size)) 114 } 115 // There may some space in the former part of the chunk. Added the bias and 116 // index, we can get locate the element by division 117 i := q.firstChunk().l + idx 118 return q.chunks[q.head+i/q.chunkLength].data[i%q.chunkLength] 119 } 120 121 // Replace assigns a new value to a given index 122 func (q *ChunkQueue[T]) Replace(idx int, val T) { 123 if idx < 0 || idx >= q.size { 124 panic(fmt.Sprintf("[ChunkQueue]: index %d os out of index [0, %d)", idx, q.size)) 125 } 126 // same with Peek() 127 i := q.firstChunk().l + idx 128 q.chunks[q.head+i/q.chunkLength].data[i%q.chunkLength] = val 129 } 130 131 // Head returns the value of the first element. This method is read-only 132 func (q *ChunkQueue[T]) Head() (T, bool) { 133 if q.Empty() { 134 return q.defaultValue, false 135 } 136 c := q.firstChunk() 137 return c.data[c.l], true 138 } 139 140 // Tail returns the value of the last element. This method is only for reading 141 // the last element, not for modification 142 func (q *ChunkQueue[T]) Tail() (T, bool) { 143 if q.Empty() { 144 return q.defaultValue, false 145 } 146 c := q.lastChunk() 147 return c.data[c.r-1], true 148 } 149 150 // extend extends the space by adding chunk(s) to the queue 151 func (q *ChunkQueue[T]) addSpace(n int) { 152 if n <= 0 { 153 panic("[ChunkQueue]: n should be greater than 0") 154 } 155 chunksNum := (n + q.chunkLength - 1) / q.chunkLength 156 157 // reallocate the chunks array if no enough space in the tail 158 if q.tail+chunksNum+1 >= len(q.chunks) { 159 q.adjustChunksArray(chunksNum) 160 } 161 162 for i := 0; i < chunksNum; i++ { 163 c := q.chunkPool.Get().(*chunk[T]) 164 c.queue = q 165 q.chunks[q.tail] = c 166 if q.tail > q.head { 167 c.prev = q.chunks[q.tail-1] 168 q.chunks[q.tail-1].next = c 169 } 170 q.tail++ 171 } 172 } 173 174 // adjustChunksArray extends/shrinks the chunk pointers array []*chunks, and 175 // eliminates the former spaces caused by popped chunks: 176 // 1. extend > 0: A positive expand represents an "extend" operation: 177 // The value is the amount of space the array should have in tail. Expand the 178 // chunks array until there is enough space. 179 // 2. extend < 0: A negative expand represents a "shrink" operation: 180 // The value of a negative extend is oblivious. The new length of the array 181 // []*chunks is max(defaultLength, tail - head + 1), which makes sure 182 func (q *ChunkQueue[T]) adjustChunksArray(extend int) { 183 used := q.tail - q.head 184 // adjust the array length. The new length should 185 var newLen int 186 switch { 187 case extend > 0: 188 newLen = len(q.chunks) 189 // Expand the array if no enough space. 190 for used+extend+1 >= newLen { 191 newLen *= 2 192 } 193 case extend < 0: 194 // for shrink, the new length is max(defaultLength, tail - head + 1) 195 newLen = used + 1 196 if newLen < defaultPitchArrayLen { 197 newLen = defaultPitchArrayLen 198 } 199 } 200 if newLen != len(q.chunks) { 201 // If the length changed, allocate a new array and do copy 202 newChunks := make([]*chunk[T], newLen) 203 copy(newChunks[:used], q.chunks[q.head:q.tail]) 204 q.chunks = newChunks 205 } else if q.head > 0 { 206 // If the new array length remains the same, then there is no need to 207 // create a new array, and only move the elements to front slots 208 copy(q.chunks[:used], q.chunks[q.head:q.tail]) 209 for i := used; i < q.tail; i++ { 210 q.chunks[i] = nil 211 } 212 } 213 q.tail -= q.head 214 q.head = 0 215 } 216 217 // Push enqueues an element to tail 218 func (q *ChunkQueue[T]) Push(v T) { 219 c := q.lastChunk() 220 if c.r == q.chunkLength { 221 q.addSpace(1) 222 c = q.lastChunk() 223 } 224 225 c.data[c.r] = v 226 c.r++ 227 q.size++ 228 } 229 230 // PushMany enqueues multiple elements at a time 231 func (q *ChunkQueue[T]) PushMany(vals ...T) { 232 cnt, n := 0, len(vals) 233 c := q.lastChunk() 234 if q.Cap()-q.Len() < n { 235 q.addSpace(n - (q.chunkLength - c.r)) 236 } 237 238 if c.r == q.chunkLength { 239 c = c.next 240 } 241 242 var addLen int 243 for n > 0 { 244 addLen = q.chunkLength - c.r 245 if addLen > n { 246 addLen = n 247 } 248 copy(c.data[c.r:c.r+addLen], vals[cnt:cnt+addLen]) 249 c.r += addLen 250 q.size += addLen 251 cnt += addLen 252 c = c.next 253 n -= addLen 254 } 255 } 256 257 // Pop dequeues an element from head. The second return value is true on 258 // success, and false if the queue is empty and no element can be popped 259 func (q *ChunkQueue[T]) Pop() (T, bool) { 260 if q.Empty() { 261 return q.defaultValue, false 262 } 263 264 c := q.firstChunk() 265 v := c.data[c.l] 266 c.data[c.l] = q.defaultValue 267 c.l++ 268 q.size-- 269 270 if c.l == q.chunkLength { 271 q.popChunk() 272 } 273 return v, true 274 } 275 276 func (q *ChunkQueue[T]) popChunk() { 277 c := q.firstChunk() 278 if c.next == nil { 279 q.addSpace(1) 280 } 281 q.chunks[q.head] = nil 282 q.head++ 283 q.chunks[q.head].prev = nil 284 285 c.reset() 286 q.chunkPool.Put(c) 287 } 288 289 // PopAll dequeues all elements in the queue 290 func (q *ChunkQueue[T]) PopAll() []T { 291 v, _ := q.PopMany(q.Len()) 292 return v 293 } 294 295 // PopMany dequeues n elements at a time. The second return value is true 296 // if n elements were popped out, and false otherwise. 297 func (q *ChunkQueue[T]) PopMany(n int) ([]T, bool) { 298 if n < 0 { 299 panic(fmt.Sprintf("negative pop number %v", n)) 300 } 301 302 ok := n <= q.size 303 if q.size < n { 304 n = q.size 305 } 306 307 res := make([]T, n) 308 cnt := 0 309 for i := q.head; i < q.tail && cnt < n; i++ { 310 c := q.chunks[i] 311 popLen := c.len() 312 if n-cnt < popLen { 313 popLen = n - cnt 314 } 315 for j := 0; j < popLen; j++ { 316 res[cnt+j] = c.data[c.l+j] 317 c.data[c.l+j] = q.defaultValue 318 } 319 c.l += popLen 320 cnt += popLen 321 q.size -= popLen 322 323 if c.l == q.chunkLength { 324 q.popChunk() 325 } 326 } 327 return res, ok 328 } 329 330 // Clear clears the queue and shrinks the chunks array 331 func (q *ChunkQueue[T]) Clear() { 332 if !q.Empty() { 333 emptyChunk := make([]T, q.chunkLength) 334 for i := q.head; i < q.tail; i++ { 335 q.size -= q.chunks[i].len() 336 copy(q.chunks[i].data[:], emptyChunk[:]) 337 q.popChunk() 338 } 339 } 340 // Shrink the chunks array 341 q.Shrink() 342 } 343 344 // Shrink shrinks the space of the chunks array 345 func (q *ChunkQueue[T]) Shrink() { 346 q.adjustChunksArray(-1) 347 } 348 349 // Range iterates the queue from head to the first element e that f(e) returns 350 // false, or to the end if f() is true for all elements. 351 func (q *ChunkQueue[T]) Range(f func(e T) bool) { 352 var c *chunk[T] 353 for i := q.head; i < q.tail; i++ { 354 c = q.chunks[i] 355 for j := c.l; j < c.r; j++ { 356 if !f(c.data[j]) { 357 return 358 } 359 } 360 } 361 } 362 363 // RangeWithIndex iterates the queue with index from head. the first element e 364 // with index i that f(i, e) is false, or to tail if f() is true for all elements. 365 func (q *ChunkQueue[T]) RangeWithIndex(f func(idx int, e T) bool) { 366 var c *chunk[T] 367 idx := 0 368 for i := q.head; i < q.tail; i++ { 369 c = q.chunks[i] 370 for j := c.l; j < c.r; j++ { 371 if !f(idx, c.data[j]) { 372 return 373 } 374 idx++ 375 } 376 } 377 } 378 379 // RangeAndPop iterate the queue from head, and pop the element til the first 380 // element e that f(e) is false, or all elements if f(e) is true for all elements. 381 // This method is more convenient than Peek and Pop 382 func (q *ChunkQueue[T]) RangeAndPop(f func(e T) bool) { 383 var c *chunk[T] 384 385 for i := q.head; !q.Empty() && i < q.tail; i++ { 386 c = q.chunks[i] 387 for j := c.l; j < c.r; j++ { 388 if f(c.data[j]) { 389 c.data[c.l] = q.defaultValue 390 c.l++ 391 q.size-- 392 } else { 393 return 394 } 395 } 396 if c.l == q.chunkLength { 397 q.popChunk() 398 } 399 } 400 }