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  }