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  }