├── CONTRIBUTORS ├── LICENSE ├── README ├── cache.go └── cache_test.go /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | This is a list of people who have contributed code to go-cache. They, or their 2 | employers, are the copyright holders of the contributed code. Contributed code 3 | is subject to the license restrictions listed in LICENSE (as they were when the 4 | code was contributed.) 5 | 6 | Dustin Sallings 7 | Sergey Shepelev 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Patrick Mylund Nielsen and the go-cache contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | go-cache is an in-memory key:value store/cache similar to memcached that is 2 | suitable for applications running on a single machine. Its major advantage is 3 | that, being essentially a thread-safe map[string]interface{} with expiration 4 | times, it doesn't need to serialize or transmit its contents over the network. 5 | 6 | Any object can be stored, for a given duration or forever, and the cache can be 7 | safely used by multiple goroutines. 8 | 9 | Although go-cache isn't meant to be used as a persistent datastore, the entire 10 | cache may be saved to and loaded from a file (or any io.Reader/Writer) to 11 | recover from downtime quickly. 12 | 13 | == Installation 14 | 15 | go get github.com/pmylund/go-cache 16 | 17 | == Usage 18 | 19 | import "github.com/pmylund/go-cache" 20 | 21 | // Create a cache with a default expiration time of 5 minutes, and which 22 | // purges expired items every 30 seconds 23 | c := cache.New(5*time.Minute, 30*time.Second) 24 | 25 | // Set the value of the key "foo" to "bar", with the default expiration time 26 | c.Set("foo", "bar", 0) 27 | 28 | // Set the value of the key "baz" to 42, with no expiration time 29 | // (the item won't be removed until it is re-set, or removed using 30 | // c.Delete("baz") 31 | c.Set("baz", 42, -1) 32 | 33 | // Get the string associated with the key "foo" from the cache 34 | foo, found := c.Get("foo") 35 | if found { 36 | fmt.Println(foo) 37 | } 38 | 39 | // Since Go is statically typed, and cache values can be anything, type 40 | // assertion is needed when values are being passed to functions that don't 41 | // take arbitrary types, (i.e. interface{}). The simplest way to do this for 42 | // values which will only be used once--e.g. for passing to another 43 | // function--is: 44 | foo, found := c.Get("foo") 45 | if found { 46 | MyFunction(foo.(string)) 47 | } 48 | 49 | // This gets tedious if the value is used several times in the same function. 50 | // You might do either of the following instead: 51 | if x, found := c.Get("foo"); found { 52 | foo := x.(string) 53 | ... 54 | } 55 | // or 56 | var foo string 57 | if x, found := c.Get("foo"); found { 58 | foo = x.(string) 59 | } 60 | ... 61 | // foo can then be passed around freely as a string 62 | 63 | // Want performance? Store pointers! 64 | c.Set("foo", &MyStruct, 0) 65 | if x, found := c.Get("foo"); found { 66 | foo := x.(*MyStruct) 67 | ... 68 | } 69 | 70 | // If you store a reference type like a pointer, slice, map or channel, you 71 | // do not need to run Set if you modify the underlying data. The cached 72 | // reference points to the same memory, so if you modify a struct whose 73 | // pointer you've stored in the cache, retrieving that pointer with Get will 74 | // point you to the same data: 75 | foo := &MyStruct{Num: 1} 76 | c.Set("foo", foo, 0) 77 | ... 78 | x, _ := c.Get("foo") 79 | foo := x.(*MyStruct) 80 | fmt.Println(foo.Num) 81 | ... 82 | foo.Num++ 83 | ... 84 | x, _ := c.Get("foo") 85 | foo := x.(*MyStruct) 86 | foo.Println(foo.Num) 87 | 88 | // will print: 89 | 1 90 | 2 91 | 92 | == Reference 93 | 94 | func New(de, ci time.Duration) *Cache 95 | Return a new cache with a given default expiration duration and cleanup 96 | interval. If the expiration duration is less than 1, the items in the cache 97 | never expire (by default), and must be deleted manually. If the cleanup 98 | interval is less than one, expired items are not deleted from the cache 99 | before their next lookup or before calling DeleteExpired. 100 | 101 | func (c *Cache) Set(k string, x interface{}, d time.Duration) 102 | Add an item to the cache, replacing any existing item. If the duration is 103 | 0, the cache's default expiration time is used. If it is -1, the item never 104 | expires. 105 | 106 | func (c *Cache) Add(k string, x interface{}, d time.Duration) error 107 | Add an item to the cache only if an item doesn't already exist for the 108 | given key, or if the existing item has expired. Returns an error if not. 109 | 110 | func (c *Cache) Replace(k string, x interface{}, d time.Duration) error 111 | Set a new value for the cache key only if it already exists. Returns an 112 | error if it does not. 113 | 114 | func (c *Cache) Get(k string) (interface{}, bool) 115 | Get an item from the cache. Returns the item or nil, and a bool indicating 116 | whether the key was found. 117 | 118 | func (c *Cache) Increment(k string, n int64) error 119 | Increment an item of type int, int8, int16, int32, int64, uintptr, uint, 120 | uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the 121 | item's value is not an integer, if it was not found, or if it is not 122 | possible to increment it by n. 123 | 124 | func (c *Cache) IncrementFloat(k string, n float64) error 125 | Increment an item of type float32 or float64 by n. Returns an error if the 126 | item's value is not floating point, if it was not found, or if it is not 127 | possible to increment it by n. Pass a negative number to decrement the 128 | value. 129 | 130 | func (c *Cache) Decrement(k string, n int64) error 131 | Decrement an item of type int, int8, int16, int32, int64, uintptr, uint, 132 | uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the 133 | item's value is not an integer, if it was not found, or if it is not 134 | possible to decrement it by n. 135 | 136 | func (c *Cache) Delete(k string) 137 | Delete an item from the cache. Does nothing if the key does not exist in the 138 | cache. 139 | 140 | func (c *Cache) DeleteExpired() 141 | Delete all expired items from the cache. 142 | 143 | func (c *Cache) Flush() 144 | Delete all items from the cache. 145 | 146 | func (c *Cache) Save(w io.Writer) error 147 | Write the cache's items (using Gob) to an io.Writer. Returns an error if 148 | the serialization fails, e.g. because there are unserializable objects like 149 | channels in the cache. 150 | 151 | func (c *Cache) SaveFile(fname string) error 152 | Save the cache's items to the given filename, creating the file if it 153 | doesn't exist, and overwriting it if it does. 154 | 155 | func (c *Cache) Load(r io.Reader) error 156 | Add (Gob-serialized) cache items from an io.Reader, excluding any items 157 | with keys that already exist in the current cache. 158 | 159 | func (c *Cache) LoadFile(fname string) error 160 | Loads and adds cache items from the given filename, excluding any items 161 | with keys that already exist in the current cache. 162 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/gob" 6 | "fmt" 7 | "hash/fnv" 8 | "io" 9 | "os" 10 | "runtime" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | var ( 16 | ErrKeyExists = fmt.Errorf("item already exists") 17 | ErrCacheMiss = fmt.Errorf("item not found") 18 | ) 19 | 20 | type unexportedInterface interface { 21 | Set(string, interface{}, time.Duration) 22 | Add(string, interface{}, time.Duration) error 23 | Replace(string, interface{}, time.Duration) error 24 | Get(string) (interface{}, bool) 25 | Increment(string, int64) error 26 | IncrementFloat(string, float64) error 27 | Decrement(string, int64) error 28 | Delete(string) 29 | DeleteExpired() 30 | Flush() 31 | Save(io.Writer) error 32 | SaveFile(string) error 33 | Load(io.Reader) error 34 | LoadFile(io.Reader) error 35 | } 36 | 37 | type item struct { 38 | Object interface{} 39 | Expiration *time.Time 40 | } 41 | 42 | // Returns true if the item has expired. 43 | func (i *item) Expired() bool { 44 | if i.Expiration == nil { 45 | return false 46 | } 47 | return i.Expiration.Before(time.Now()) 48 | } 49 | 50 | type Cache struct { 51 | *cache 52 | // If this is confusing, see the comment at the bottom of New() 53 | } 54 | 55 | type cache struct { 56 | sync.Mutex 57 | defaultExpiration time.Duration 58 | items map[string]*item 59 | janitor *janitor 60 | } 61 | 62 | // Add an item to the cache, replacing any existing item. If the duration is 0, 63 | // the cache's default expiration time is used. If it is -1, the item never 64 | // expires. 65 | func (c *cache) Set(k string, x interface{}, d time.Duration) { 66 | c.Lock() 67 | c.set(k, x, d) 68 | // TODO: Calls to mu.Unlock are currently not deferred because defer 69 | // adds ~200 ns (as of go1.) 70 | c.Unlock() 71 | } 72 | 73 | func (c *cache) set(k string, x interface{}, d time.Duration) { 74 | var e *time.Time 75 | if d == 0 { 76 | d = c.defaultExpiration 77 | } 78 | if d > 0 { 79 | t := time.Now().Add(d) 80 | e = &t 81 | } 82 | c.items[k] = &item{ 83 | Object: x, 84 | Expiration: e, 85 | } 86 | } 87 | 88 | // Add an item to the cache only if an item doesn't already exist for the given 89 | // key, or if the existing item has expired. Returns an error otherwise. 90 | func (c *cache) Add(k string, x interface{}, d time.Duration) error { 91 | c.Lock() 92 | _, found := c.get(k) 93 | if found { 94 | c.Unlock() 95 | return ErrKeyExists 96 | } 97 | c.set(k, x, d) 98 | c.Unlock() 99 | return nil 100 | } 101 | 102 | // Set a new value for the cache key only if it already exists. Returns an 103 | // error if it does not. 104 | func (c *cache) Replace(k string, x interface{}, d time.Duration) error { 105 | c.Lock() 106 | _, found := c.get(k) 107 | if !found { 108 | c.Unlock() 109 | return fmt.Errorf("item %s doesn't exist", k) 110 | } 111 | c.set(k, x, d) 112 | c.Unlock() 113 | return nil 114 | } 115 | 116 | // Get an item from the cache. Returns the item or nil, and a bool indicating 117 | // whether the key was found. 118 | func (c *cache) Get(k string) (interface{}, bool) { 119 | c.Lock() 120 | x, found := c.get(k) 121 | c.Unlock() 122 | return x, found 123 | } 124 | 125 | func (c *cache) get(k string) (interface{}, bool) { 126 | item, found := c.items[k] 127 | if !found { 128 | return nil, false 129 | } 130 | if item.Expired() { 131 | c.delete(k) 132 | return nil, false 133 | } 134 | return item.Object, true 135 | } 136 | 137 | // Increment an item of type float32 or float64 by n. Returns an error if the 138 | // item's value is not floating point, if it was not found, or if it is not 139 | // possible to increment it by n. Pass a negative number to decrement the 140 | // value. 141 | func (c *cache) IncrementFloat(k string, n float64) error { 142 | c.Lock() 143 | v, found := c.items[k] 144 | if !found || v.Expired() { 145 | c.Unlock() 146 | return fmt.Errorf("item not found") 147 | } 148 | switch v.Object.(type) { 149 | case float32: 150 | v.Object = v.Object.(float32) + float32(n) 151 | case float64: 152 | v.Object = v.Object.(float64) + n 153 | default: 154 | c.Unlock() 155 | return fmt.Errorf("The value for %s does not have type float32 or float64", k) 156 | } 157 | c.Unlock() 158 | return nil 159 | } 160 | 161 | // Increment an item of type int, int8, int16, int32, int64, uintptr, uint, 162 | // uint8, uint32, or uint64 by n. Returns an error if the 163 | // item's value is not an integer, if it was not found, or if it is not 164 | // possible to increment it by n. 165 | // Wraps around on overlow. 166 | func (c *cache) Increment(k string, n uint64) (uint64, error) { 167 | c.Lock() 168 | defer c.Unlock() 169 | v, found := c.items[k] 170 | if !found || v.Expired() { 171 | return 0, ErrCacheMiss 172 | } 173 | switch v.Object.(type) { 174 | case int: 175 | v.Object = v.Object.(int) + int(n) 176 | return uint64(v.Object.(int)), nil 177 | case int8: 178 | v.Object = v.Object.(int8) + int8(n) 179 | return uint64(v.Object.(int8)), nil 180 | case int16: 181 | v.Object = v.Object.(int16) + int16(n) 182 | return uint64(v.Object.(int16)), nil 183 | case int32: 184 | v.Object = v.Object.(int32) + int32(n) 185 | return uint64(v.Object.(int32)), nil 186 | case int64: 187 | v.Object = v.Object.(int64) + int64(n) 188 | return uint64(v.Object.(int64)), nil 189 | case uint: 190 | v.Object = v.Object.(uint) + uint(n) 191 | return uint64(v.Object.(uint)), nil 192 | case uintptr: 193 | v.Object = v.Object.(uintptr) + uintptr(n) 194 | return uint64(v.Object.(uintptr)), nil 195 | case uint8: 196 | v.Object = v.Object.(uint8) + uint8(n) 197 | return uint64(v.Object.(uint8)), nil 198 | case uint16: 199 | v.Object = v.Object.(uint16) + uint16(n) 200 | return uint64(v.Object.(uint16)), nil 201 | case uint32: 202 | v.Object = v.Object.(uint32) + uint32(n) 203 | return uint64(v.Object.(uint32)), nil 204 | case uint64: 205 | v.Object = v.Object.(uint64) + n 206 | return uint64(v.Object.(uint64)), nil 207 | } 208 | return 0, fmt.Errorf("The value for %s is not an integer", k) 209 | } 210 | 211 | // Decrement an item of type int, int8, int16, int32, int64, uintptr, uint, 212 | // uint8, uint32, or uint64 by n. Returns an error if the 213 | // item's value is not an integer, if it was not found, or if it is not 214 | // possible to decrement it by n. 215 | // Stops at 0 on underflow. 216 | func (c *cache) Decrement(k string, n uint64) (uint64, error) { 217 | // TODO: Implement Increment and Decrement more cleanly. 218 | // (Cannot do Increment(k, n*-1) for uints.) 219 | c.Lock() 220 | defer c.Unlock() 221 | v, found := c.items[k] 222 | if !found || v.Expired() { 223 | return 0, ErrCacheMiss 224 | } 225 | switch v.Object.(type) { 226 | case int: 227 | vi := v.Object.(int) 228 | if vi > int(n) { 229 | v.Object = vi - int(n) 230 | } else { 231 | v.Object = int(0) 232 | } 233 | return uint64(v.Object.(int)), nil 234 | case int8: 235 | vi := v.Object.(int8) 236 | if vi > int8(n) { 237 | v.Object = vi - int8(n) 238 | } else { 239 | v.Object = int8(0) 240 | } 241 | return uint64(v.Object.(int8)), nil 242 | case int16: 243 | vi := v.Object.(int16) 244 | if vi > int16(n) { 245 | v.Object = vi - int16(n) 246 | } else { 247 | v.Object = int16(0) 248 | } 249 | return uint64(v.Object.(int16)), nil 250 | case int32: 251 | vi := v.Object.(int32) 252 | if vi > int32(n) { 253 | v.Object = vi - int32(n) 254 | } else { 255 | v.Object = int32(0) 256 | } 257 | return uint64(v.Object.(int32)), nil 258 | case int64: 259 | vi := v.Object.(int64) 260 | if vi > int64(n) { 261 | v.Object = vi - int64(n) 262 | } else { 263 | v.Object = int64(0) 264 | } 265 | return uint64(v.Object.(int64)), nil 266 | case uint: 267 | vi := v.Object.(uint) 268 | if vi > uint(n) { 269 | v.Object = vi - uint(n) 270 | } else { 271 | v.Object = uint(0) 272 | } 273 | return uint64(v.Object.(uint)), nil 274 | case uintptr: 275 | vi := v.Object.(uintptr) 276 | if vi > uintptr(n) { 277 | v.Object = vi - uintptr(n) 278 | } else { 279 | v.Object = uintptr(0) 280 | } 281 | return uint64(v.Object.(uintptr)), nil 282 | case uint8: 283 | vi := v.Object.(uint8) 284 | if vi > uint8(n) { 285 | v.Object = vi - uint8(n) 286 | } else { 287 | v.Object = uint8(0) 288 | } 289 | return uint64(v.Object.(uint8)), nil 290 | case uint16: 291 | vi := v.Object.(uint16) 292 | if vi > uint16(n) { 293 | v.Object = vi - uint16(n) 294 | } else { 295 | v.Object = uint16(0) 296 | } 297 | return uint64(v.Object.(uint16)), nil 298 | case uint32: 299 | vi := v.Object.(uint32) 300 | if vi > uint32(n) { 301 | v.Object = vi - uint32(n) 302 | } else { 303 | v.Object = uint32(0) 304 | } 305 | return uint64(v.Object.(uint32)), nil 306 | case uint64: 307 | vi := v.Object.(uint64) 308 | if vi > uint64(n) { 309 | v.Object = vi - uint64(n) 310 | } else { 311 | v.Object = uint64(0) 312 | } 313 | return uint64(v.Object.(uint64)), nil 314 | } 315 | return 0, fmt.Errorf("The value for %s is not an integer", k) 316 | } 317 | 318 | // Delete an item from the cache. Does nothing if the key is not in the cache. 319 | func (c *cache) Delete(k string) (found bool) { 320 | c.Lock() 321 | _, found = c.get(k) 322 | c.delete(k) 323 | c.Unlock() 324 | return 325 | } 326 | 327 | func (c *cache) delete(k string) { 328 | delete(c.items, k) 329 | } 330 | 331 | // Delete all expired items from the cache. 332 | func (c *cache) DeleteExpired() { 333 | c.Lock() 334 | for k, v := range c.items { 335 | if v.Expired() { 336 | c.delete(k) 337 | } 338 | } 339 | c.Unlock() 340 | } 341 | 342 | // Write the cache's items (using Gob) to an io.Writer. 343 | func (c *cache) Save(w io.Writer) (err error) { 344 | enc := gob.NewEncoder(w) 345 | 346 | defer func() { 347 | if x := recover(); x != nil { 348 | err = fmt.Errorf("Error registering item types with Gob library") 349 | } 350 | }() 351 | for _, v := range c.items { 352 | gob.Register(v.Object) 353 | } 354 | err = enc.Encode(&c.items) 355 | return 356 | } 357 | 358 | // Save the cache's items to the given filename, creating the file if it 359 | // doesn't exist, and overwriting it if it does. 360 | func (c *cache) SaveFile(fname string) error { 361 | fp, err := os.Create(fname) 362 | if err != nil { 363 | return err 364 | } 365 | err = c.Save(fp) 366 | if err != nil { 367 | fp.Close() 368 | return err 369 | } 370 | return fp.Close() 371 | } 372 | 373 | // Add (Gob-serialized) cache items from an io.Reader, excluding any items with 374 | // keys that already exist in the current cache. 375 | func (c *cache) Load(r io.Reader) error { 376 | dec := gob.NewDecoder(r) 377 | items := map[string]*item{} 378 | err := dec.Decode(&items) 379 | if err == nil { 380 | for k, v := range items { 381 | _, found := c.items[k] 382 | if !found { 383 | c.items[k] = v 384 | } 385 | } 386 | } 387 | return err 388 | } 389 | 390 | // Load and add cache items from the given filename, excluding any items with 391 | // keys that already exist in the current cache. 392 | func (c *cache) LoadFile(fname string) error { 393 | fp, err := os.Open(fname) 394 | if err != nil { 395 | return err 396 | } 397 | err = c.Load(fp) 398 | if err != nil { 399 | fp.Close() 400 | return err 401 | } 402 | return fp.Close() 403 | } 404 | 405 | // Delete all items from the cache. 406 | func (c *cache) Flush() { 407 | c.Lock() 408 | c.items = map[string]*item{} 409 | c.Unlock() 410 | } 411 | 412 | type janitor struct { 413 | Interval time.Duration 414 | stop chan bool 415 | } 416 | 417 | func (j *janitor) Run(c *cache) { 418 | j.stop = make(chan bool) 419 | tick := time.Tick(j.Interval) 420 | for { 421 | select { 422 | case <-tick: 423 | c.DeleteExpired() 424 | case <-j.stop: 425 | return 426 | } 427 | } 428 | } 429 | 430 | func stopJanitor(c *Cache) { 431 | c.janitor.stop <- true 432 | } 433 | 434 | func runJanitor(c *cache, ci time.Duration) { 435 | j := &janitor{ 436 | Interval: ci, 437 | } 438 | c.janitor = j 439 | go j.Run(c) 440 | } 441 | 442 | func newCache(de time.Duration) *cache { 443 | if de == 0 { 444 | de = -1 445 | } 446 | c := &cache{ 447 | defaultExpiration: de, 448 | items: map[string]*item{}, 449 | } 450 | return c 451 | } 452 | 453 | // Return a new cache with a given default expiration duration and cleanup 454 | // interval. If the expiration duration is less than 1, the items in the cache 455 | // never expire (by default), and must be deleted manually. If the cleanup 456 | // interval is less than one, expired items are not deleted from the cache 457 | // before their next lookup or before calling DeleteExpired. 458 | func New(defaultExpiration, cleanupInterval time.Duration) *Cache { 459 | c := newCache(defaultExpiration) 460 | // This trick ensures that the janitor goroutine (which--granted it 461 | // was enabled--is running DeleteExpired on c forever) does not keep 462 | // the returned C object from being garbage collected. When it is 463 | // garbage collected, the finalizer stops the janitor goroutine, after 464 | // which c can be collected. 465 | C := &Cache{c} 466 | if cleanupInterval > 0 { 467 | runJanitor(c, cleanupInterval) 468 | runtime.SetFinalizer(C, stopJanitor) 469 | } 470 | return C 471 | } 472 | 473 | type unexportedShardedCache struct { 474 | *shardedCache 475 | } 476 | 477 | type shardedCache struct { 478 | m uint32 479 | cs []*cache 480 | janitor *shardedJanitor 481 | } 482 | 483 | func (sc *shardedCache) bucket(k string) *cache { 484 | h := fnv.New32() 485 | h.Write([]byte(k)) 486 | n := binary.BigEndian.Uint32(h.Sum(nil)) 487 | return sc.cs[n%sc.m] 488 | } 489 | 490 | func (sc *shardedCache) Set(k string, x interface{}, d time.Duration) { 491 | sc.bucket(k).Set(k, x, d) 492 | } 493 | 494 | func (sc *shardedCache) Add(k string, x interface{}, d time.Duration) error { 495 | return sc.bucket(k).Add(k, x, d) 496 | } 497 | 498 | func (sc *shardedCache) Replace(k string, x interface{}, d time.Duration) error { 499 | return sc.bucket(k).Replace(k, x, d) 500 | } 501 | 502 | func (sc *shardedCache) Get(k string) (interface{}, bool) { 503 | return sc.bucket(k).Get(k) 504 | } 505 | 506 | func (sc *shardedCache) Increment(k string, n uint64) (uint64, error) { 507 | return sc.bucket(k).Increment(k, n) 508 | } 509 | 510 | func (sc *shardedCache) IncrementFloat(k string, n float64) error { 511 | return sc.bucket(k).IncrementFloat(k, n) 512 | } 513 | 514 | func (sc *shardedCache) Decrement(k string, n uint64) (uint64, error) { 515 | return sc.bucket(k).Decrement(k, n) 516 | } 517 | 518 | func (sc *shardedCache) Delete(k string) { 519 | sc.bucket(k).Delete(k) 520 | } 521 | 522 | func (sc *shardedCache) DeleteExpired() { 523 | for _, v := range sc.cs { 524 | v.DeleteExpired() 525 | } 526 | } 527 | 528 | func (sc *shardedCache) Flush() { 529 | for _, v := range sc.cs { 530 | v.Flush() 531 | } 532 | } 533 | 534 | type shardedJanitor struct { 535 | Interval time.Duration 536 | stop chan bool 537 | } 538 | 539 | func (j *shardedJanitor) Run(sc *shardedCache) { 540 | j.stop = make(chan bool) 541 | tick := time.Tick(j.Interval) 542 | for { 543 | select { 544 | case <-tick: 545 | sc.DeleteExpired() 546 | case <-j.stop: 547 | return 548 | } 549 | } 550 | } 551 | 552 | func stopShardedJanitor(sc *unexportedShardedCache) { 553 | sc.janitor.stop <- true 554 | } 555 | 556 | func runShardedJanitor(sc *shardedCache, ci time.Duration) { 557 | j := &shardedJanitor{ 558 | Interval: ci, 559 | } 560 | sc.janitor = j 561 | go j.Run(sc) 562 | } 563 | 564 | func newShardedCache(n int, de time.Duration) *shardedCache { 565 | sc := &shardedCache{ 566 | m: uint32(n - 1), 567 | cs: make([]*cache, n), 568 | } 569 | for i := 0; i < n; i++ { 570 | c := &cache{ 571 | defaultExpiration: de, 572 | items: map[string]*item{}, 573 | } 574 | sc.cs[i] = c 575 | } 576 | return sc 577 | } 578 | 579 | func unexportedNewSharded(shards int, defaultExpiration, cleanupInterval time.Duration) *unexportedShardedCache { 580 | if defaultExpiration == 0 { 581 | defaultExpiration = -1 582 | } 583 | sc := newShardedCache(shards, defaultExpiration) 584 | SC := &unexportedShardedCache{sc} 585 | if cleanupInterval > 0 { 586 | runShardedJanitor(sc, cleanupInterval) 587 | runtime.SetFinalizer(SC, stopShardedJanitor) 588 | } 589 | return SC 590 | } 591 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "runtime" 7 | "strconv" 8 | "sync" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | type TestStruct struct { 14 | Num int 15 | Children []*TestStruct 16 | } 17 | 18 | func TestCache(t *testing.T) { 19 | tc := New(0, 0) 20 | 21 | a, found := tc.Get("a") 22 | if found || a != nil { 23 | t.Error("Getting A found value that shouldn't exist:", a) 24 | } 25 | 26 | b, found := tc.Get("b") 27 | if found || b != nil { 28 | t.Error("Getting B found value that shouldn't exist:", b) 29 | } 30 | 31 | c, found := tc.Get("c") 32 | if found || c != nil { 33 | t.Error("Getting C found value that shouldn't exist:", c) 34 | } 35 | 36 | tc.Set("a", 1, 0) 37 | tc.Set("b", "b", 0) 38 | tc.Set("c", 3.5, 0) 39 | 40 | x, found := tc.Get("a") 41 | if !found { 42 | t.Error("a was not found while getting a2") 43 | } 44 | if x == nil { 45 | t.Error("x for a is nil") 46 | } else if a2 := x.(int); a2+2 != 3 { 47 | t.Error("a2 (which should be 1) plus 2 does not equal 3; value:", a2) 48 | } 49 | 50 | x, found = tc.Get("b") 51 | if !found { 52 | t.Error("b was not found while getting b2") 53 | } 54 | if x == nil { 55 | t.Error("x for b is nil") 56 | } else if b2 := x.(string); b2+"B" != "bB" { 57 | t.Error("b2 (which should be b) plus B does not equal bB; value:", b2) 58 | } 59 | 60 | x, found = tc.Get("c") 61 | if !found { 62 | t.Error("c was not found while getting c2") 63 | } 64 | if x == nil { 65 | t.Error("x for c is nil") 66 | } else if c2 := x.(float64); c2+1.2 != 4.7 { 67 | t.Error("c2 (which should be 3.5) plus 1.2 does not equal 4.7; value:", c2) 68 | } 69 | } 70 | 71 | func TestCacheTimes(t *testing.T) { 72 | var found bool 73 | 74 | tc := New(50*time.Millisecond, 1*time.Millisecond) 75 | tc.Set("a", 1, 0) 76 | tc.Set("b", 2, -1) 77 | tc.Set("c", 3, 20*time.Millisecond) 78 | tc.Set("d", 4, 70*time.Millisecond) 79 | 80 | <-time.After(25 * time.Millisecond) 81 | _, found = tc.Get("c") 82 | if found { 83 | t.Error("Found c when it should have been automatically deleted") 84 | } 85 | 86 | <-time.After(30 * time.Millisecond) 87 | _, found = tc.Get("a") 88 | if found { 89 | t.Error("Found a when it should have been automatically deleted") 90 | } 91 | 92 | _, found = tc.Get("b") 93 | if !found { 94 | t.Error("Did not find b even though it was set to never expire") 95 | } 96 | 97 | _, found = tc.Get("d") 98 | if !found { 99 | t.Error("Did not find d even though it was set to expire later than the default") 100 | } 101 | 102 | <-time.After(20 * time.Millisecond) 103 | _, found = tc.Get("d") 104 | if found { 105 | t.Error("Found d when it should have been automatically deleted (later than the default)") 106 | } 107 | } 108 | 109 | func TestStorePointerToStruct(t *testing.T) { 110 | tc := New(0, 0) 111 | tc.Set("foo", &TestStruct{Num: 1}, 0) 112 | x, found := tc.Get("foo") 113 | if !found { 114 | t.Fatal("*TestStruct was not found for foo") 115 | } 116 | foo := x.(*TestStruct) 117 | foo.Num++ 118 | 119 | y, found := tc.Get("foo") 120 | if !found { 121 | t.Fatal("*TestStruct was not found for foo (second time)") 122 | } 123 | bar := y.(*TestStruct) 124 | if bar.Num != 2 { 125 | t.Fatal("TestStruct.Num is not 2") 126 | } 127 | } 128 | 129 | func TestIncrementUint(t *testing.T) { 130 | tc := New(0, 0) 131 | tc.Set("tuint", uint(1), 0) 132 | _, err := tc.Increment("tuint", 2) 133 | if err != nil { 134 | t.Error("Error incrementing:", err) 135 | } 136 | 137 | x, found := tc.Get("tuint") 138 | if !found { 139 | t.Error("tuint was not found") 140 | } 141 | if x.(uint) != 3 { 142 | t.Error("tuint is not 3:", x) 143 | } 144 | } 145 | 146 | func TestIncrementUintptr(t *testing.T) { 147 | tc := New(0, 0) 148 | tc.Set("tuintptr", uintptr(1), 0) 149 | _, err := tc.Increment("tuintptr", 2) 150 | if err != nil { 151 | t.Error("Error incrementing:", err) 152 | } 153 | 154 | x, found := tc.Get("tuintptr") 155 | if !found { 156 | t.Error("tuintptr was not found") 157 | } 158 | if x.(uintptr) != 3 { 159 | t.Error("tuintptr is not 3:", x) 160 | } 161 | } 162 | 163 | func TestIncrementUint8(t *testing.T) { 164 | tc := New(0, 0) 165 | tc.Set("tuint8", uint8(1), 0) 166 | _, err := tc.Increment("tuint8", 2) 167 | if err != nil { 168 | t.Error("Error incrementing:", err) 169 | } 170 | 171 | x, found := tc.Get("tuint8") 172 | if !found { 173 | t.Error("tuint8 was not found") 174 | } 175 | if x.(uint8) != 3 { 176 | t.Error("tuint8 is not 3:", x) 177 | } 178 | } 179 | 180 | func TestIncrementUint16(t *testing.T) { 181 | tc := New(0, 0) 182 | tc.Set("tuint16", uint16(1), 0) 183 | _, err := tc.Increment("tuint16", 2) 184 | if err != nil { 185 | t.Error("Error incrementing:", err) 186 | } 187 | 188 | x, found := tc.Get("tuint16") 189 | if !found { 190 | t.Error("tuint16 was not found") 191 | } 192 | if x.(uint16) != 3 { 193 | t.Error("tuint16 is not 3:", x) 194 | } 195 | } 196 | 197 | func TestIncrementUint32(t *testing.T) { 198 | tc := New(0, 0) 199 | tc.Set("tuint32", uint32(1), 0) 200 | _, err := tc.Increment("tuint32", 2) 201 | if err != nil { 202 | t.Error("Error incrementing:", err) 203 | } 204 | 205 | x, found := tc.Get("tuint32") 206 | if !found { 207 | t.Error("tuint32 was not found") 208 | } 209 | if x.(uint32) != 3 { 210 | t.Error("tuint32 is not 3:", x) 211 | } 212 | } 213 | 214 | func TestIncrementUint64(t *testing.T) { 215 | tc := New(0, 0) 216 | tc.Set("tuint64", uint64(1), 0) 217 | _, err := tc.Increment("tuint64", 2) 218 | if err != nil { 219 | t.Error("Error incrementing:", err) 220 | } 221 | 222 | x, found := tc.Get("tuint64") 223 | if !found { 224 | t.Error("tuint64 was not found") 225 | } 226 | if x.(uint64) != 3 { 227 | t.Error("tuint64 is not 3:", x) 228 | } 229 | } 230 | 231 | func TestIncrementInt(t *testing.T) { 232 | tc := New(0, 0) 233 | tc.Set("tint", 1, 0) 234 | _, err := tc.Increment("tint", 2) 235 | if err != nil { 236 | t.Error("Error incrementing:", err) 237 | } 238 | x, found := tc.Get("tint") 239 | if !found { 240 | t.Error("tint was not found") 241 | } 242 | if x.(int) != 3 { 243 | t.Error("tint is not 3:", x) 244 | } 245 | } 246 | 247 | func TestIncrementInt8(t *testing.T) { 248 | tc := New(0, 0) 249 | tc.Set("tint8", int8(1), 0) 250 | _, err := tc.Increment("tint8", 2) 251 | if err != nil { 252 | t.Error("Error incrementing:", err) 253 | } 254 | x, found := tc.Get("tint8") 255 | if !found { 256 | t.Error("tint8 was not found") 257 | } 258 | if x.(int8) != 3 { 259 | t.Error("tint8 is not 3:", x) 260 | } 261 | } 262 | 263 | func TestIncrementInt16(t *testing.T) { 264 | tc := New(0, 0) 265 | tc.Set("tint16", int16(1), 0) 266 | _, err := tc.Increment("tint16", 2) 267 | if err != nil { 268 | t.Error("Error incrementing:", err) 269 | } 270 | x, found := tc.Get("tint16") 271 | if !found { 272 | t.Error("tint16 was not found") 273 | } 274 | if x.(int16) != 3 { 275 | t.Error("tint16 is not 3:", x) 276 | } 277 | } 278 | 279 | func TestIncrementInt32(t *testing.T) { 280 | tc := New(0, 0) 281 | tc.Set("tint32", int32(1), 0) 282 | _, err := tc.Increment("tint32", 2) 283 | if err != nil { 284 | t.Error("Error incrementing:", err) 285 | } 286 | x, found := tc.Get("tint32") 287 | if !found { 288 | t.Error("tint32 was not found") 289 | } 290 | if x.(int32) != 3 { 291 | t.Error("tint32 is not 3:", x) 292 | } 293 | } 294 | 295 | func TestIncrementInt64(t *testing.T) { 296 | tc := New(0, 0) 297 | tc.Set("tint64", int64(1), 0) 298 | _, err := tc.Increment("tint64", 2) 299 | if err != nil { 300 | t.Error("Error incrementing:", err) 301 | } 302 | x, found := tc.Get("tint64") 303 | if !found { 304 | t.Error("tint64 was not found") 305 | } 306 | if x.(int64) != 3 { 307 | t.Error("tint64 is not 3:", x) 308 | } 309 | } 310 | 311 | func TestDecrementInt64(t *testing.T) { 312 | tc := New(0, 0) 313 | tc.Set("int64", int64(5), 0) 314 | _, err := tc.Decrement("int64", 2) 315 | if err != nil { 316 | t.Error("Error decrementing:", err) 317 | } 318 | x, found := tc.Get("int64") 319 | if !found { 320 | t.Error("int64 was not found") 321 | } 322 | if x.(int64) != 3 { 323 | t.Error("int64 is not 3:", x) 324 | } 325 | } 326 | 327 | func TestAdd(t *testing.T) { 328 | tc := New(0, 0) 329 | err := tc.Add("foo", "bar", 0) 330 | if err != nil { 331 | t.Error("Couldn't add foo even though it shouldn't exist") 332 | } 333 | err = tc.Add("foo", "baz", 0) 334 | if err == nil { 335 | t.Error("Successfully added another foo when it should have returned an error") 336 | } 337 | } 338 | 339 | func TestReplace(t *testing.T) { 340 | tc := New(0, 0) 341 | err := tc.Replace("foo", "bar", 0) 342 | if err == nil { 343 | t.Error("Replaced foo when it shouldn't exist") 344 | } 345 | tc.Set("foo", "bar", 0) 346 | err = tc.Replace("foo", "bar", 0) 347 | if err != nil { 348 | t.Error("Couldn't replace existing key foo") 349 | } 350 | } 351 | 352 | func TestDelete(t *testing.T) { 353 | tc := New(0, 0) 354 | tc.Set("foo", "bar", 0) 355 | tc.Delete("foo") 356 | x, found := tc.Get("foo") 357 | if found { 358 | t.Error("foo was found, but it should have been deleted") 359 | } 360 | if x != nil { 361 | t.Error("x is not nil:", x) 362 | } 363 | } 364 | 365 | func TestFlush(t *testing.T) { 366 | tc := New(0, 0) 367 | tc.Set("foo", "bar", 0) 368 | tc.Set("baz", "yes", 0) 369 | tc.Flush() 370 | x, found := tc.Get("foo") 371 | if found { 372 | t.Error("foo was found, but it should have been deleted") 373 | } 374 | if x != nil { 375 | t.Error("x is not nil:", x) 376 | } 377 | x, found = tc.Get("baz") 378 | if found { 379 | t.Error("baz was found, but it should have been deleted") 380 | } 381 | if x != nil { 382 | t.Error("x is not nil:", x) 383 | } 384 | } 385 | 386 | func TestIncrementOverflowInt(t *testing.T) { 387 | tc := New(0, 0) 388 | tc.Set("int8", int8(127), 0) 389 | _, err := tc.Increment("int8", 1) 390 | if err != nil { 391 | t.Error("Error incrementing int8:", err) 392 | } 393 | x, _ := tc.Get("int8") 394 | int8 := x.(int8) 395 | if int8 != -128 { 396 | t.Error("int8 did not overflow as expected; value:", int8) 397 | } 398 | 399 | } 400 | 401 | func TestIncrementOverflowUint(t *testing.T) { 402 | tc := New(0, 0) 403 | tc.Set("uint8", uint8(255), 0) 404 | _, err := tc.Increment("uint8", 1) 405 | if err != nil { 406 | t.Error("Error incrementing int8:", err) 407 | } 408 | x, _ := tc.Get("uint8") 409 | uint8 := x.(uint8) 410 | if uint8 != 0 { 411 | t.Error("uint8 did not overflow as expected; value:", uint8) 412 | } 413 | } 414 | 415 | func TestDecrementUnderflowUint(t *testing.T) { 416 | tc := New(0, 0) 417 | tc.Set("uint8", uint8(0), 0) 418 | _, err := tc.Decrement("uint8", 1) 419 | if err != nil { 420 | t.Error("Error decrementing int8:", err) 421 | } 422 | x, _ := tc.Get("uint8") 423 | uint8 := x.(uint8) 424 | if uint8 != 0 { 425 | t.Error("uint8 was not capped at 0; value:", uint8) 426 | } 427 | } 428 | 429 | func TestCacheSerialization(t *testing.T) { 430 | tc := New(0, 0) 431 | testFillAndSerialize(t, tc) 432 | 433 | // Check if gob.Register behaves properly even after multiple gob.Register 434 | // on c.Items (many of which will be the same type) 435 | testFillAndSerialize(t, tc) 436 | } 437 | 438 | func testFillAndSerialize(t *testing.T, tc *Cache) { 439 | tc.Set("a", "a", 0) 440 | tc.Set("b", "b", 0) 441 | tc.Set("c", "c", 0) 442 | tc.Set("expired", "foo", 1*time.Millisecond) 443 | tc.Set("*struct", &TestStruct{Num: 1}, 0) 444 | tc.Set("[]struct", []TestStruct{ 445 | {Num: 2}, 446 | {Num: 3}, 447 | }, 0) 448 | tc.Set("[]*struct", []*TestStruct{ 449 | &TestStruct{Num: 4}, 450 | &TestStruct{Num: 5}, 451 | }, 0) 452 | tc.Set("structception", &TestStruct{ 453 | Num: 42, 454 | Children: []*TestStruct{ 455 | &TestStruct{Num: 6174}, 456 | &TestStruct{Num: 4716}, 457 | }, 458 | }, 0) 459 | 460 | fp := &bytes.Buffer{} 461 | err := tc.Save(fp) 462 | if err != nil { 463 | t.Fatal("Couldn't save cache to fp:", err) 464 | } 465 | 466 | oc := New(0, 0) 467 | err = oc.Load(fp) 468 | if err != nil { 469 | t.Fatal("Couldn't load cache from fp:", err) 470 | } 471 | 472 | a, found := oc.Get("a") 473 | if !found { 474 | t.Error("a was not found") 475 | } 476 | if a.(string) != "a" { 477 | t.Error("a is not a") 478 | } 479 | 480 | b, found := oc.Get("b") 481 | if !found { 482 | t.Error("b was not found") 483 | } 484 | if b.(string) != "b" { 485 | t.Error("b is not b") 486 | } 487 | 488 | c, found := oc.Get("c") 489 | if !found { 490 | t.Error("c was not found") 491 | } 492 | if c.(string) != "c" { 493 | t.Error("c is not c") 494 | } 495 | 496 | <-time.After(5 * time.Millisecond) 497 | _, found = oc.Get("expired") 498 | if found { 499 | t.Error("expired was found") 500 | } 501 | 502 | s1, found := oc.Get("*struct") 503 | if !found { 504 | t.Error("*struct was not found") 505 | } 506 | if s1.(*TestStruct).Num != 1 { 507 | t.Error("*struct.Num is not 1") 508 | } 509 | 510 | s2, found := oc.Get("[]struct") 511 | if !found { 512 | t.Error("[]struct was not found") 513 | } 514 | s2r := s2.([]TestStruct) 515 | if len(s2r) != 2 { 516 | t.Error("Length of s2r is not 2") 517 | } 518 | if s2r[0].Num != 2 { 519 | t.Error("s2r[0].Num is not 2") 520 | } 521 | if s2r[1].Num != 3 { 522 | t.Error("s2r[1].Num is not 3") 523 | } 524 | 525 | s3, found := oc.get("[]*struct") 526 | if !found { 527 | t.Error("[]*struct was not found") 528 | } 529 | s3r := s3.([]*TestStruct) 530 | if len(s3r) != 2 { 531 | t.Error("Length of s3r is not 2") 532 | } 533 | if s3r[0].Num != 4 { 534 | t.Error("s3r[0].Num is not 4") 535 | } 536 | if s3r[1].Num != 5 { 537 | t.Error("s3r[1].Num is not 5") 538 | } 539 | 540 | s4, found := oc.get("structception") 541 | if !found { 542 | t.Error("structception was not found") 543 | } 544 | s4r := s4.(*TestStruct) 545 | if len(s4r.Children) != 2 { 546 | t.Error("Length of s4r.Children is not 2") 547 | } 548 | if s4r.Children[0].Num != 6174 { 549 | t.Error("s4r.Children[0].Num is not 6174") 550 | } 551 | if s4r.Children[1].Num != 4716 { 552 | t.Error("s4r.Children[1].Num is not 4716") 553 | } 554 | } 555 | 556 | func TestFileSerialization(t *testing.T) { 557 | tc := New(0, 0) 558 | tc.Add("a", "a", 0) 559 | tc.Add("b", "b", 0) 560 | f, err := ioutil.TempFile("", "go-cache-cache.dat") 561 | if err != nil { 562 | t.Fatal("Couldn't create cache file:", err) 563 | } 564 | fname := f.Name() 565 | f.Close() 566 | tc.SaveFile(fname) 567 | 568 | oc := New(0, 0) 569 | oc.Add("a", "aa", 0) // this should not be overwritten 570 | err = oc.LoadFile(fname) 571 | if err != nil { 572 | t.Error(err) 573 | } 574 | a, found := oc.Get("a") 575 | if !found { 576 | t.Error("a was not found") 577 | } 578 | astr := a.(string) 579 | if astr != "aa" { 580 | if astr == "a" { 581 | t.Error("a was overwritten") 582 | } else { 583 | t.Error("a is not aa") 584 | } 585 | } 586 | b, found := oc.Get("b") 587 | if !found { 588 | t.Error("b was not found") 589 | } 590 | if b.(string) != "b" { 591 | t.Error("b is not b") 592 | } 593 | } 594 | 595 | func TestSerializeUnserializable(t *testing.T) { 596 | tc := New(0, 0) 597 | ch := make(chan bool, 1) 598 | ch <- true 599 | tc.Set("chan", ch, 0) 600 | fp := &bytes.Buffer{} 601 | err := tc.Save(fp) // this should fail gracefully 602 | if err.Error() != "gob NewTypeObject can't handle type: chan bool" { 603 | t.Error("Error from Save was not gob NewTypeObject can't handle type chan bool:", err) 604 | } 605 | } 606 | 607 | func BenchmarkCacheGet(b *testing.B) { 608 | b.StopTimer() 609 | tc := New(0, 0) 610 | tc.Set("foo", "bar", 0) 611 | b.StartTimer() 612 | for i := 0; i < b.N; i++ { 613 | tc.Get("foo") 614 | } 615 | } 616 | 617 | func BenchmarkMutexMapGet(b *testing.B) { 618 | b.StopTimer() 619 | m := map[string]string{ 620 | "foo": "bar", 621 | } 622 | mu := sync.Mutex{} 623 | b.StartTimer() 624 | for i := 0; i < b.N; i++ { 625 | mu.Lock() 626 | _, _ = m["foo"] 627 | mu.Unlock() 628 | } 629 | } 630 | 631 | func BenchmarkCacheGetConcurrent(b *testing.B) { 632 | b.StopTimer() 633 | tc := New(0, 0) 634 | tc.Set("foo", "bar", 0) 635 | wg := new(sync.WaitGroup) 636 | workers := runtime.NumCPU() 637 | each := b.N / workers 638 | wg.Add(workers) 639 | b.StartTimer() 640 | for i := 0; i < workers; i++ { 641 | go func() { 642 | for j := 0; j < each; j++ { 643 | tc.Get("foo") 644 | } 645 | wg.Done() 646 | }() 647 | } 648 | wg.Wait() 649 | } 650 | 651 | func BenchmarkMutexMapGetConcurrent(b *testing.B) { 652 | b.StopTimer() 653 | m := map[string]string{ 654 | "foo": "bar", 655 | } 656 | mu := sync.Mutex{} 657 | wg := new(sync.WaitGroup) 658 | workers := runtime.NumCPU() 659 | each := b.N / workers 660 | wg.Add(workers) 661 | b.StartTimer() 662 | for i := 0; i < workers; i++ { 663 | go func() { 664 | for j := 0; j < each; j++ { 665 | mu.Lock() 666 | _, _ = m["foo"] 667 | mu.Unlock() 668 | } 669 | wg.Done() 670 | }() 671 | } 672 | wg.Wait() 673 | } 674 | 675 | func BenchmarkCacheGetManyConcurrent(b *testing.B) { 676 | // This is the same as BenchmarkCacheGetConcurrent, but its result 677 | // can be compared against BenchmarkShardedCacheGetManyConcurrent. 678 | b.StopTimer() 679 | n := 10000 680 | tc := New(0, 0) 681 | keys := make([]string, n) 682 | for i := 0; i < n; i++ { 683 | k := "foo" + strconv.Itoa(n) 684 | keys[i] = k 685 | tc.Set(k, "bar", 0) 686 | } 687 | each := b.N / n 688 | wg := new(sync.WaitGroup) 689 | wg.Add(n) 690 | for _, v := range keys { 691 | go func() { 692 | for j := 0; j < each; j++ { 693 | tc.Get(v) 694 | } 695 | wg.Done() 696 | }() 697 | } 698 | b.StartTimer() 699 | wg.Wait() 700 | } 701 | 702 | func BenchmarkShardedCacheGetManyConcurrent(b *testing.B) { 703 | b.StopTimer() 704 | n := 10000 705 | tsc := unexportedNewSharded(20, 0, 0) 706 | keys := make([]string, n) 707 | for i := 0; i < n; i++ { 708 | k := "foo" + strconv.Itoa(n) 709 | keys[i] = k 710 | tsc.Set(k, "bar", 0) 711 | } 712 | each := b.N / n 713 | wg := new(sync.WaitGroup) 714 | wg.Add(n) 715 | for _, v := range keys { 716 | go func() { 717 | for j := 0; j < each; j++ { 718 | tsc.Get(v) 719 | } 720 | wg.Done() 721 | }() 722 | } 723 | b.StartTimer() 724 | wg.Wait() 725 | } 726 | 727 | func BenchmarkCacheSet(b *testing.B) { 728 | b.StopTimer() 729 | tc := New(0, 0) 730 | b.StartTimer() 731 | for i := 0; i < b.N; i++ { 732 | tc.Set("foo", "bar", 0) 733 | } 734 | } 735 | 736 | func BenchmarkMutexMapSet(b *testing.B) { 737 | b.StopTimer() 738 | m := map[string]string{} 739 | mu := sync.Mutex{} 740 | b.StartTimer() 741 | for i := 0; i < b.N; i++ { 742 | mu.Lock() 743 | m["foo"] = "bar" 744 | mu.Unlock() 745 | } 746 | } 747 | 748 | func BenchmarkCacheSetDelete(b *testing.B) { 749 | b.StopTimer() 750 | tc := New(0, 0) 751 | b.StartTimer() 752 | for i := 0; i < b.N; i++ { 753 | tc.Set("foo", "bar", 0) 754 | tc.Delete("foo") 755 | } 756 | } 757 | 758 | func BenchmarkMutexMapSetDelete(b *testing.B) { 759 | b.StopTimer() 760 | m := map[string]string{} 761 | mu := sync.Mutex{} 762 | b.StartTimer() 763 | for i := 0; i < b.N; i++ { 764 | mu.Lock() 765 | m["foo"] = "bar" 766 | mu.Unlock() 767 | mu.Lock() 768 | delete(m, "foo") 769 | mu.Unlock() 770 | } 771 | } 772 | 773 | func BenchmarkCacheSetDeleteSingleLock(b *testing.B) { 774 | b.StopTimer() 775 | tc := New(0, 0) 776 | b.StartTimer() 777 | for i := 0; i < b.N; i++ { 778 | tc.Lock() 779 | tc.set("foo", "bar", 0) 780 | tc.delete("foo") 781 | tc.Unlock() 782 | } 783 | } 784 | 785 | func BenchmarkMutexMapSetDeleteSingleLock(b *testing.B) { 786 | b.StopTimer() 787 | m := map[string]string{} 788 | mu := sync.Mutex{} 789 | b.StartTimer() 790 | for i := 0; i < b.N; i++ { 791 | mu.Lock() 792 | m["foo"] = "bar" 793 | delete(m, "foo") 794 | mu.Unlock() 795 | } 796 | } 797 | --------------------------------------------------------------------------------