github.com/maypok86/otter@v1.2.1/internal/stats/counter.go (about) 1 // Copyright (c) 2023 Alexey Mayshev. All rights reserved. 2 // Copyright (c) 2021 Andrey Pechkurov 3 // 4 // Copyright notice. This code is a fork of xsync.Counter from this file with some changes: 5 // https://github.com/puzpuzpuz/xsync/blob/main/counter.go 6 // 7 // Use of this source code is governed by a MIT license that can be found 8 // at https://github.com/puzpuzpuz/xsync/blob/main/LICENSE 9 10 package stats 11 12 import ( 13 "sync" 14 "sync/atomic" 15 16 "github.com/maypok86/otter/internal/xmath" 17 "github.com/maypok86/otter/internal/xruntime" 18 ) 19 20 // pool for P tokens. 21 var tokenPool sync.Pool 22 23 // a P token is used to point at the current OS thread (P) 24 // on which the goroutine is run; exact identity of the thread, 25 // as well as P migration tolerance, is not important since 26 // it's used to as a best effort mechanism for assigning 27 // concurrent operations (goroutines) to different stripes of 28 // the counter. 29 type token struct { 30 idx uint32 31 padding [xruntime.CacheLineSize - 4]byte 32 } 33 34 // A counter is a striped int64 counter. 35 // 36 // Should be preferred over a single atomically updated int64 37 // counter in high contention scenarios. 38 // 39 // A counter must not be copied after first use. 40 type counter struct { 41 shards []cshard 42 mask uint32 43 } 44 45 type cshard struct { 46 c int64 47 padding [xruntime.CacheLineSize - 8]byte 48 } 49 50 // newCounter creates a new counter instance. 51 func newCounter() *counter { 52 nshards := xmath.RoundUpPowerOf2(xruntime.Parallelism()) 53 return &counter{ 54 shards: make([]cshard, nshards), 55 mask: nshards - 1, 56 } 57 } 58 59 // increment increments the counter by 1. 60 func (c *counter) increment() { 61 c.add(1) 62 } 63 64 // decrement decrements the counter by 1. 65 func (c *counter) decrement() { 66 c.add(-1) 67 } 68 69 // add adds the delta to the counter. 70 func (c *counter) add(delta int64) { 71 t, ok := tokenPool.Get().(*token) 72 if !ok { 73 t = &token{} 74 t.idx = xruntime.Fastrand() 75 } 76 for { 77 shard := &c.shards[t.idx&c.mask] 78 cnt := atomic.LoadInt64(&shard.c) 79 if atomic.CompareAndSwapInt64(&shard.c, cnt, cnt+delta) { 80 break 81 } 82 // Give a try with another randomly selected shard. 83 t.idx = xruntime.Fastrand() 84 } 85 tokenPool.Put(t) 86 } 87 88 // value returns the current counter value. 89 // The returned value may not include all of the latest operations in 90 // presence of concurrent modifications of the counter. 91 func (c *counter) value() int64 { 92 v := int64(0) 93 for i := 0; i < len(c.shards); i++ { 94 shard := &c.shards[i] 95 v += atomic.LoadInt64(&shard.c) 96 } 97 return v 98 } 99 100 // reset resets the counter to zero. 101 // This method should only be used when it is known that there are 102 // no concurrent modifications of the counter. 103 func (c *counter) reset() { 104 for i := 0; i < len(c.shards); i++ { 105 shard := &c.shards[i] 106 atomic.StoreInt64(&shard.c, 0) 107 } 108 }