2014-12-22 13:24:49 +08:00
package cache
import (
2014-12-22 23:37:59 +08:00
"crypto/rand"
"math"
"math/big"
insecurerand "math/rand"
"os"
2014-12-22 13:24:49 +08:00
"runtime"
"time"
2022-03-19 03:19:19 +08:00
"golang.org/x/exp/constraints"
2014-12-22 13:24:49 +08:00
)
// This is an experimental and unexported (for now) attempt at making a cache
// with better algorithmic complexity than the standard one, namely by
// preventing write locks of the entire cache when an item is added. As of the
2014-12-22 23:37:59 +08:00
// time of writing, the overhead of selecting buckets results in cache
// operations being about twice as slow as for the standard cache with small
// total cache sizes, and faster for larger ones.
2014-12-22 13:24:49 +08:00
//
// See cache_test.go for a few benchmarks.
2022-03-19 03:19:19 +08:00
type unexportedShardedCache [ V constraints . Ordered ] struct {
* shardedCache [ V ]
2014-12-22 13:24:49 +08:00
}
2022-03-19 03:19:19 +08:00
type shardedCache [ V constraints . Ordered ] struct {
2014-12-22 23:37:59 +08:00
seed uint32
2014-12-22 13:24:49 +08:00
m uint32
2022-03-19 03:19:19 +08:00
cs [ ] * orderedCache [ string , V ]
janitor * shardedJanitor [ V ]
2014-12-22 13:24:49 +08:00
}
2014-12-22 23:37:59 +08:00
// djb2 with better shuffling. 5x faster than FNV with the hash.Hash overhead.
func djb33 ( seed uint32 , k string ) uint32 {
var (
l = uint32 ( len ( k ) )
d = 5381 + seed + l
i = uint32 ( 0 )
)
// Why is all this 5x faster than a for loop?
if l >= 4 {
for i < l - 4 {
d = ( d * 33 ) ^ uint32 ( k [ i ] )
d = ( d * 33 ) ^ uint32 ( k [ i + 1 ] )
d = ( d * 33 ) ^ uint32 ( k [ i + 2 ] )
d = ( d * 33 ) ^ uint32 ( k [ i + 3 ] )
i += 4
}
}
switch l - i {
case 1 :
case 2 :
d = ( d * 33 ) ^ uint32 ( k [ i ] )
case 3 :
d = ( d * 33 ) ^ uint32 ( k [ i ] )
d = ( d * 33 ) ^ uint32 ( k [ i + 1 ] )
case 4 :
d = ( d * 33 ) ^ uint32 ( k [ i ] )
d = ( d * 33 ) ^ uint32 ( k [ i + 1 ] )
d = ( d * 33 ) ^ uint32 ( k [ i + 2 ] )
}
return d ^ ( d >> 16 )
}
2022-03-19 03:19:19 +08:00
func ( sc * shardedCache [ V ] ) bucket ( k string ) * orderedCache [ string , V ] {
2014-12-22 23:37:59 +08:00
return sc . cs [ djb33 ( sc . seed , k ) % sc . m ]
2014-12-22 13:24:49 +08:00
}
2022-03-19 03:19:19 +08:00
func ( sc * shardedCache [ V ] ) Set ( k string , x V , d time . Duration ) {
2014-12-22 13:24:49 +08:00
sc . bucket ( k ) . Set ( k , x , d )
}
2022-03-19 03:19:19 +08:00
func ( sc * shardedCache [ V ] ) Add ( k string , x V , d time . Duration ) error {
2014-12-22 13:24:49 +08:00
return sc . bucket ( k ) . Add ( k , x , d )
}
2022-03-19 03:19:19 +08:00
func ( sc * shardedCache [ V ] ) Replace ( k string , x V , d time . Duration ) error {
2014-12-22 13:24:49 +08:00
return sc . bucket ( k ) . Replace ( k , x , d )
}
2022-03-25 04:41:39 +08:00
func ( sc * shardedCache [ V ] ) Get ( k string ) ( V , bool ) {
2014-12-22 13:24:49 +08:00
return sc . bucket ( k ) . Get ( k )
}
2022-03-19 03:19:19 +08:00
func ( sc * shardedCache [ V ] ) Increment ( k string , n V ) error {
_ , err := sc . bucket ( k ) . Increment ( k , n )
return err
2014-12-22 13:24:49 +08:00
}
2022-03-19 03:19:19 +08:00
func ( sc * shardedCache [ V ] ) Delete ( k string ) {
2014-12-22 13:24:49 +08:00
sc . bucket ( k ) . Delete ( k )
}
2022-03-19 03:19:19 +08:00
func ( sc * shardedCache [ V ] ) DeleteExpired ( ) {
2014-12-22 13:24:49 +08:00
for _ , v := range sc . cs {
v . DeleteExpired ( )
}
}
2014-12-22 23:37:59 +08:00
// Returns the items in the cache. This may include items that have expired,
// but have not yet been cleaned up. If this is significant, the Expiration
// fields of the items should be checked. Note that explicit synchronization
// is needed to use a cache and its corresponding Items() return values at
// the same time, as the maps are shared.
2022-03-19 03:19:19 +08:00
func ( sc * shardedCache [ V ] ) Items ( ) [ ] map [ string ] Item [ V ] {
res := make ( [ ] map [ string ] Item [ V ] , len ( sc . cs ) )
2014-12-22 23:37:59 +08:00
for i , v := range sc . cs {
res [ i ] = v . Items ( )
}
return res
}
2022-03-19 03:19:19 +08:00
func ( sc * shardedCache [ V ] ) Flush ( ) {
2014-12-22 13:24:49 +08:00
for _ , v := range sc . cs {
v . Flush ( )
}
}
2022-03-19 03:19:19 +08:00
type shardedJanitor [ V constraints . Ordered ] struct {
2014-12-22 13:24:49 +08:00
Interval time . Duration
stop chan bool
}
2022-03-19 03:19:19 +08:00
func ( j * shardedJanitor [ V ] ) Run ( sc * shardedCache [ V ] ) {
2014-12-22 13:24:49 +08:00
j . stop = make ( chan bool )
tick := time . Tick ( j . Interval )
for {
select {
case <- tick :
sc . DeleteExpired ( )
case <- j . stop :
return
}
}
}
2022-03-19 03:19:19 +08:00
func stopShardedJanitor [ V constraints . Ordered ] ( sc * unexportedShardedCache [ V ] ) {
2014-12-22 13:24:49 +08:00
sc . janitor . stop <- true
}
2022-03-19 03:19:19 +08:00
func runShardedJanitor [ V constraints . Ordered ] ( sc * shardedCache [ V ] , ci time . Duration ) {
j := & shardedJanitor [ V ] {
2014-12-22 13:24:49 +08:00
Interval : ci ,
}
sc . janitor = j
go j . Run ( sc )
}
2022-03-19 03:19:19 +08:00
func newShardedCache [ V constraints . Ordered ] ( n int , de time . Duration ) * shardedCache [ V ] {
2014-12-22 23:37:59 +08:00
max := big . NewInt ( 0 ) . SetUint64 ( uint64 ( math . MaxUint32 ) )
rnd , err := rand . Int ( rand . Reader , max )
var seed uint32
if err != nil {
os . Stderr . Write ( [ ] byte ( "WARNING: go-cache's newShardedCache failed to read from the system CSPRNG (/dev/urandom or equivalent.) Your system's security may be compromised. Continuing with an insecure seed.\n" ) )
seed = insecurerand . Uint32 ( )
} else {
seed = uint32 ( rnd . Uint64 ( ) )
}
2022-03-19 03:19:19 +08:00
sc := & shardedCache [ V ] {
2014-12-22 23:37:59 +08:00
seed : seed ,
m : uint32 ( n ) ,
2022-03-19 03:19:19 +08:00
cs : make ( [ ] * orderedCache [ string , V ] , n ) ,
2014-12-22 13:24:49 +08:00
}
for i := 0 ; i < n ; i ++ {
2022-03-19 03:19:19 +08:00
c := & orderedCache [ string , V ] {
Cache : & Cache [ string , V ] {
cache : & cache [ string , V ] {
defaultExpiration : de ,
items : map [ string ] Item [ V ] { } ,
} ,
} ,
2014-12-22 13:24:49 +08:00
}
sc . cs [ i ] = c
}
return sc
}
2022-03-19 03:19:19 +08:00
func unexportedNewSharded [ V constraints . Ordered ] ( defaultExpiration , cleanupInterval time . Duration , shards int ) * unexportedShardedCache [ V ] {
2014-12-22 13:24:49 +08:00
if defaultExpiration == 0 {
defaultExpiration = - 1
}
2022-03-19 03:19:19 +08:00
sc := newShardedCache [ V ] ( shards , defaultExpiration )
SC := & unexportedShardedCache [ V ] { sc }
2014-12-22 13:24:49 +08:00
if cleanupInterval > 0 {
runShardedJanitor ( sc , cleanupInterval )
2022-03-19 03:19:19 +08:00
runtime . SetFinalizer ( SC , stopShardedJanitor [ V ] )
2014-12-22 13:24:49 +08:00
}
return SC
}