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 }