github.com/fiatjaf/generic-ristretto@v0.0.1/ring.go (about)

     1  /*
     2   * Copyright 2019 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package ristretto
    18  
    19  import (
    20  	"sync"
    21  )
    22  
    23  // ringConsumer is the user-defined object responsible for receiving and
    24  // processing items in batches when buffers are drained.
    25  type ringConsumer interface {
    26  	Push([]uint64) bool
    27  }
    28  
    29  // ringStripe is a singular ring buffer that is not concurrent safe.
    30  type ringStripe struct {
    31  	cons ringConsumer
    32  	data []uint64
    33  	capa int
    34  }
    35  
    36  func newRingStripe(cons ringConsumer, capa int64) *ringStripe {
    37  	return &ringStripe{
    38  		cons: cons,
    39  		data: make([]uint64, 0, capa),
    40  		capa: int(capa),
    41  	}
    42  }
    43  
    44  // Push appends an item in the ring buffer and drains (copies items and
    45  // sends to Consumer) if full.
    46  func (s *ringStripe) Push(item uint64) {
    47  	s.data = append(s.data, item)
    48  	// Decide if the ring buffer should be drained.
    49  	if len(s.data) >= s.capa {
    50  		// Send elements to consumer and create a new ring stripe.
    51  		if s.cons.Push(s.data) {
    52  			s.data = make([]uint64, 0, s.capa)
    53  		} else {
    54  			s.data = s.data[:0]
    55  		}
    56  	}
    57  }
    58  
    59  // ringBuffer stores multiple buffers (stripes) and distributes Pushed items
    60  // between them to lower contention.
    61  //
    62  // This implements the "batching" process described in the BP-Wrapper paper
    63  // (section III part A).
    64  type ringBuffer struct {
    65  	pool *sync.Pool
    66  }
    67  
    68  // newRingBuffer returns a striped ring buffer. The Consumer in ringConfig will
    69  // be called when individual stripes are full and need to drain their elements.
    70  func newRingBuffer(cons ringConsumer, capa int64) *ringBuffer {
    71  	// LOSSY buffers use a very simple sync.Pool for concurrently reusing
    72  	// stripes. We do lose some stripes due to GC (unheld items in sync.Pool
    73  	// are cleared), but the performance gains generally outweigh the small
    74  	// percentage of elements lost. The performance primarily comes from
    75  	// low-level runtime functions used in the standard library that aren't
    76  	// available to us (such as runtime_procPin()).
    77  	return &ringBuffer{
    78  		pool: &sync.Pool{
    79  			New: func() interface{} { return newRingStripe(cons, capa) },
    80  		},
    81  	}
    82  }
    83  
    84  // Push adds an element to one of the internal stripes and possibly drains if
    85  // the stripe becomes full.
    86  func (b *ringBuffer) Push(item uint64) {
    87  	// Reuse or create a new stripe.
    88  	stripe := b.pool.Get().(*ringStripe)
    89  	stripe.Push(item)
    90  	b.pool.Put(stripe)
    91  }