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  }