github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/container/README.md (about) 1 # Containers for TiFlow 2 3 ## ChunkQueue 4 5 An memory efficient queue implementation in Golang. 6 7 ### Features 8 9 - Support for Generics (Go 1.18+) 10 - Chunked memory allocation. Compared to slice, it's GC-friendly and free of 11 redundant copy when pushing and popping 12 - Support iterator operations and multiple range methods 13 14 ### Design 15 The design of ChunkQueue is as follows. 16 ChunkQueue divides the memory allocation into equal-length segments. A segment 17 is called a chunk. ChunkQueue maintains an array []*chunks, addresses of chunks 18 in use. 19 20 The length of each chunk, `chunkLength`, is determined during the queue's initialization. 21 For example, the chunk length of a ChunkQueue[T] is `max{16, 1Kb / size of type T}`. 22 For each chunk, interval [l, r) are valid elements. 23 24 Push() puts an element at the free index of the last chunk. A new chunk will be 25 added if the last chunk is complete. Pop() pops an element from the first non-empty 26 index of the first chunk. The whole chunk will be poped if it becomes empty. 27 28 PushMany() and PopMany(int) operates multiple elements at a time, optimized by 29 copy(). The second return value of PopMany(n int) indicates whether the number 30 of elements popped meets the input n. 31 32 For a ChunkQueue, there is at most one non-full chunk at both ends. 33 34 35 ![ChunkQueue Design](../../docs/media/chunkqueue_design.jpg) 36 37 ### APIs 38 #### ChunkQueue[T] 39 40 Basic APIs 41 ```Go 42 Empty() bool 43 Len() int 44 Head() (T, bool) // read-only value of the head, bool is false for empty queue 45 Tail() (T, bool) // read-only value of the tail, bool is false for empty queue 46 Peek(index int) T // read-only value of a given index 47 Replace(index int, val T) // replace the value of a given index with val 48 49 Push(val T) // Enqueue; 50 Pop() (T, bool) // Dequeue Op; The bool is false for a empty queue 51 52 PushMany(vals ...T) 53 PopMany(n int) ([]T, bool) // If Len() < n, the bool is false and all elements will be poped 54 PopAll() []T 55 56 Shrink() // Shrink shrinks the space of the chunks array 57 Clear() // Clear clears the queue and shrinks the chunks array 58 ``` 59 Iterator related APIs 60 ```Go 61 GetIterator(index int) *ChunkQueueIterator[T] // nil for index not in [0, Len()) 62 63 First() *ChunkQueueIterator[T] 64 Last() *ChunkQueueIterator[T] 65 66 Begin() *ChunkQueueIterator[T] // an alias of First(), interchangeable with First() 67 End() *ChunkQueueIterator[T] // an invalid iterator representing the end 68 ``` 69 70 GetIterator returns an iterator of a given index. Nil for invalid indices. 71 72 First() and Last() returns the iterator of the first and last elements, 73 respectively. The validity of them depends on whether the queue is empty. 74 75 End() is a special invalid iterator of the queue representing the end, whose 76 predecessor is Last() 77 78 #### ChunkQueueIterator[T] 79 ```Go 80 Valid() // true if the element of the iterator is in queue 81 Index() int // index of the iterator staring from 0; -1 for invalid ones 82 83 Value() T // Value is read-only 84 Set(v T) 85 86 Next() bool 87 Prev() bool 88 ``` 89 90 Next() and Prev() update the current iterator to its next and previous iterator, 91 respectively. The return value is boolean, which also indicates the validity of 92 the updated iterator. 93 94 For an end iterator, Prev() will update it to Last() and return true. For other 95 invalid iterators, Prev() and Next() are no-ops and always return false. 96 97 Using invalid iterators may panic. 98 99 ### Range and Iteration 100 101 Range methods are counterparts of the range of slice. They scan the queue 102 from the head to tail. 103 104 ```Go 105 // Range iterates the queue from head to the first element e that f(e) returns 106 // false, or to the end if f() is true for all elements. 107 Range(f func(e T) bool) 108 109 // RangeWithIndex iterates the queue with index from head to the first element 110 // e that f(index, e) is false, or to the end if f() is true for all elements. 111 RangeWithIndex(f func(idx int, e T) bool) 112 113 // RangeAndPop iterates and pops elements from head to the first element e that 114 // f(e) is false, or all elements if f(e) is true for all elements. 115 // This method is more convenient than Peek and Pop 116 RangeAndPop(f func(e T) bool) 117 ``` 118 119 ### Examples 120 121 ```Go 122 q := NewChunkQueue[int]() 123 124 q.Push(1) // 1 125 q.PushMany(2, 3, 4) // 1, 2, 3, 4 126 127 l := q.Len() // l = 4 128 129 val, ok := q.Pop() // 1, true 130 vals, ok := q.PopMany(2) // [2, 3], true 131 vals, ok := q.PopMany(2) // [4], false 132 133 s := []int{5, 6, 7, 8, 9, 10} 134 q.PushMany(s...) // 5, 6, 7, 8, 9, 10 135 vals, ok = q.PopAll() // [5, 6, 7, 8, 9, 10], true 136 137 q.Range(func(v int) bool { 138 if v < 10 { 139 return false 140 } 141 // ... 142 return true 143 }) 144 145 cnt := 0 146 q.RangeAndPop(func (v int) bool { 147 if v > 7 { 148 return false 149 } 150 cnt += v // cnt = 5 + 6 + 7 151 // return true 152 }) 153 154 ``` 155 Manipulating invalid iterators may incur panic. Please be careful and do checks 156 since a valid iterator can become invalid after the element was popped out. 157 158 Specifically, if the iterator is the head element while iterating, can only 159 Pop() after the Next() operation. 160 161 ```Go 162 for it := q.First(); it.Valid(); it.Next() { // forward iteration 163 // ... ops cannot pop 164 } 165 166 for it := q.First(); it.Valid(); { 167 // ... 168 it.Next() 169 q.Pop() // can only Pop() after Next() 170 } 171 172 for it := q.Last(); it.Valid(); it.Next() { 173 // ... 174 } 175 ``` 176 177 The return value of Next() and Prev() are booleans which also indicate the 178 validity. So, the backward iteration can also be written as follows: 179 ```Go 180 for it := q.End(); it.Prev(); { 181 // ... 182 } 183 ``` 184 185 ### Benchmark 186 ChunkQueue uses less time and less memory per operation. 187 188 ```log 189 BenchmarkPush/Push-ChunkQueue-8 269918079 4.359 ns/op 8 B/op 190 BenchmarkPush/Push-Slice-8 356492558 9.950 ns/op 42 B/op 191 BenchmarkPush/Push-3rdPartyDeque-8 58088172 19.78 ns/op 24 B/op 192 193 BenchmarkPushMany 194 BenchmarkPushMany/PushMany-ChunkDeque-8 82057828 24.89 ns/op 78 B/op 195 BenchmarkPushMany/PushMany-ChunkDeque-1By1-8 30489063 36.43 ns/op 78 B/op 196 BenchmarkPushMany/PushMany-Slice-8 100000000 95.58 ns/op 271 B/op 197 BenchmarkPushMany/PushMany-3rdPartyDeque-8 8411283 125.9 ns/op 218 B/op 198 199 BenchmarkPopMany 200 BenchmarkPopMany/PopMany-ChunkDeque-8 843638862 6.266 ns/op 201 BenchmarkPopMany/PopMany-Slice-8 332659428 4.096 ns/op 202 BenchmarkPopMany/PopMany-3rdPartyDeque-8 193823920 13.74 ns/op 203 204 BenchmarkLoopPop 205 BenchmarkChunkQueueLoopPop/ChunkQueue-RangeAndPop-8 448408568 2.649 ns/op 206 BenchmarkChunkQueueLoopPop/ChunkQueue-IterateAndPop-8 326951571 3.751 ns/op 207 BenchmarkChunkQueueLoopPop/ChunkQueue-PeekAndPop-8 291695371 4.160 ns/op 208 209 BenchmarkIterate 210 BenchmarkIterate/Iterate-ChunkQueue-by-iterator-8 538929010 2.227 ns/op 211 BenchmarkIterate/Iterate-ChunkQueue-by-Peek-8 479762835 2.582 ns/op 212 BenchmarkIterate/Iterate-ChunkQueue-by-Range-8 554261636 2.162 ns/op 213 BenchmarkIterate/Iterate-Slice-byLoop-8 1000000000 2.221 ns/op 214 BenchmarkIterate/Iterate-3rdPartyDeque-byRange-8 472465203 10.94 ns/op 215 BenchmarkIterate/Iterate-3rdPartyDeque-byPeek-8 1000000 6213 ns/op 216 ```