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  ```