Skip to content

Commit

Permalink
rename benchmarks; update changelog; update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
zekroTJA committed May 4, 2024
1 parent 8094c45 commit 1bb2fbc
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 44 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## v2.0.0

- Add type parameters for the key and value type of a TimedMap and corresponding constructor functions.
- Remove the section system for better simplicity and usability.
- Remove deprecated `SetExpire` method.
- Update documentation.
- Update minimum required Go version to v1.19.0.

## v1.5.2

- Multiple race conditions have been fixed (by @ShivamKumar2002 in https://github.com/zekroTJA/timedmap/pull/8)

## v1.5.1

- Add [`FromMap`](https://pkg.go.dev/github.com/zekroTJA/timedmap#FromMap) constructor which can be used to create a `TimedMap` from an existing map with the given expiration values for each key-value pair.

## v1.4.0

- Add `SetExpires` method to match `Section` interface and match naming scheme of the other expire-related endpoints.
Expand Down
46 changes: 46 additions & 0 deletions benchmarks/v1.5.2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
goos: windows
goarch: amd64
pkg: github.com/zekroTJA/timedmap
cpu: AMD Ryzen 7 5800X 8-Core Processor
BenchmarkSetValues-16 1882472 678.8 ns/op 252 B/op 3 allocs/op
BenchmarkSetValues-16 1968472 670.1 ns/op 245 B/op 3 allocs/op
BenchmarkSetValues-16 2001894 655.4 ns/op 242 B/op 3 allocs/op
BenchmarkSetValues-16 2135914 635.2 ns/op 232 B/op 3 allocs/op
BenchmarkSetValues-16 2148526 631.1 ns/op 231 B/op 3 allocs/op
BenchmarkSetValues-16 2146839 633.8 ns/op 231 B/op 3 allocs/op
BenchmarkSetValues-16 2160771 630.3 ns/op 230 B/op 3 allocs/op
BenchmarkSetValues-16 2159397 634.7 ns/op 230 B/op 3 allocs/op
BenchmarkSetValues-16 2115090 636.0 ns/op 233 B/op 3 allocs/op
BenchmarkSetValues-16 2125656 633.5 ns/op 232 B/op 3 allocs/op
BenchmarkSetGetValues-16 1862566 729.9 ns/op 254 B/op 3 allocs/op
BenchmarkSetGetValues-16 1837540 723.3 ns/op 256 B/op 3 allocs/op
BenchmarkSetGetValues-16 1820694 702.8 ns/op 258 B/op 3 allocs/op
BenchmarkSetGetValues-16 1830153 708.0 ns/op 257 B/op 3 allocs/op
BenchmarkSetGetValues-16 1831124 716.2 ns/op 257 B/op 3 allocs/op
BenchmarkSetGetValues-16 1837466 709.6 ns/op 256 B/op 3 allocs/op
BenchmarkSetGetValues-16 1820491 710.9 ns/op 258 B/op 3 allocs/op
BenchmarkSetGetValues-16 1851866 724.8 ns/op 255 B/op 3 allocs/op
BenchmarkSetGetValues-16 1834436 720.6 ns/op 257 B/op 3 allocs/op
BenchmarkSetGetValues-16 1760607 670.2 ns/op 264 B/op 3 allocs/op
BenchmarkSetGetRemoveValues-16 3977371 300.9 ns/op 15 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 3949048 300.1 ns/op 16 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 3930261 301.7 ns/op 15 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 3964603 302.3 ns/op 15 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 3970574 301.3 ns/op 15 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 3970753 303.3 ns/op 16 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 3957939 302.4 ns/op 16 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 3965145 301.8 ns/op 16 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 3981002 302.6 ns/op 15 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 3960450 301.5 ns/op 16 B/op 1 allocs/op
BenchmarkSetGetSameKey-16 8956666 132.5 ns/op 8 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 9140398 132.4 ns/op 8 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 9072358 133.2 ns/op 8 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 9228426 130.8 ns/op 8 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 9120446 132.2 ns/op 8 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 9067176 132.3 ns/op 8 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 9152479 132.0 ns/op 8 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 9019464 131.8 ns/op 8 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 9069512 131.6 ns/op 8 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 8987463 133.1 ns/op 8 B/op 0 allocs/op
PASS
ok github.com/zekroTJA/timedmap 575.199s
46 changes: 46 additions & 0 deletions benchmarks/v2.0.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
goos: windows
goarch: amd64
pkg: github.com/zekroTJA/timedmap
cpu: AMD Ryzen 7 5800X 8-Core Processor
BenchmarkSetValues-16 3184940 339.4 ns/op 120 B/op 1 allocs/op
BenchmarkSetValues-16 3601794 438.6 ns/op 159 B/op 1 allocs/op
BenchmarkSetValues-16 3893646 375.8 ns/op 152 B/op 1 allocs/op
BenchmarkSetValues-16 4121847 372.0 ns/op 147 B/op 1 allocs/op
BenchmarkSetValues-16 4067539 368.1 ns/op 148 B/op 1 allocs/op
BenchmarkSetValues-16 4130247 367.4 ns/op 147 B/op 1 allocs/op
BenchmarkSetValues-16 3752602 376.5 ns/op 155 B/op 1 allocs/op
BenchmarkSetValues-16 3900349 372.1 ns/op 151 B/op 1 allocs/op
BenchmarkSetValues-16 4099213 373.1 ns/op 147 B/op 1 allocs/op
BenchmarkSetValues-16 4141642 371.3 ns/op 146 B/op 1 allocs/op
BenchmarkSetGetValues-16 3391387 406.2 ns/op 117 B/op 1 allocs/op
BenchmarkSetGetValues-16 3330207 389.4 ns/op 118 B/op 1 allocs/op
BenchmarkSetGetValues-16 3308163 381.0 ns/op 118 B/op 1 allocs/op
BenchmarkSetGetValues-16 3349912 383.9 ns/op 118 B/op 1 allocs/op
BenchmarkSetGetValues-16 3350439 382.1 ns/op 118 B/op 1 allocs/op
BenchmarkSetGetValues-16 3322348 384.1 ns/op 118 B/op 1 allocs/op
BenchmarkSetGetValues-16 3327039 384.1 ns/op 118 B/op 1 allocs/op
BenchmarkSetGetValues-16 3311404 385.7 ns/op 118 B/op 1 allocs/op
BenchmarkSetGetValues-16 3230353 402.7 ns/op 119 B/op 1 allocs/op
BenchmarkSetGetValues-16 3298658 387.8 ns/op 118 B/op 1 allocs/op
BenchmarkSetGetRemoveValues-16 8977050 133.6 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetRemoveValues-16 8609907 134.7 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetRemoveValues-16 8937481 133.7 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetRemoveValues-16 8970057 135.2 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetRemoveValues-16 9006141 133.3 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetRemoveValues-16 8969152 134.2 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetRemoveValues-16 8832172 133.9 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetRemoveValues-16 8906787 133.8 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetRemoveValues-16 9071329 136.9 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetRemoveValues-16 8991464 132.3 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17527305 69.23 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17328144 68.63 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17363221 68.14 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17746070 69.00 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17612041 69.11 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17479461 69.11 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17608268 68.99 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17113422 67.34 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17349764 69.17 ns/op 0 B/op 0 allocs/op
BenchmarkSetGetSameKey-16 17481498 68.93 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/zekroTJA/timedmap/v2 621.443s
11 changes: 4 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ module github.com/zekroTJA/timedmap/v2

go 1.19

require (
github.com/stretchr/testify v1.7.0
github.com/zekroTJA/timedmap v1.5.2
)
require github.com/stretchr/testify v1.9.0

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.1.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
github.com/stretchr/objx v0.5.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 8 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zekroTJA/timedmap v1.5.2 h1:5bhWBdyvekLHLrZu8cNJB6iCpIQl4bGaG4HTmPbTNKY=
github.com/zekroTJA/timedmap v1.5.2/go.mod h1:Go4uPxMN1Wjl5IgO6HYD1tM9IQhkYEVqcrrdsI4ljXo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
68 changes: 33 additions & 35 deletions timedmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import (
"time"
)

// Callback is a function which can be called when a key-value-pair has expired.
type Callback[TVal any] func(value TVal)

// TimedMap contains a map with all key-value pairs,
// and a timer, which cleans the map in the set
// tick durations from expired keys.
// TimedMap is a key-value map with lifetimes attached to values.
// Expired values are removed on access or via a cleanup coroutine,
// which can be enabled via the StartCleanerInternal method.
type TimedMap[TKey comparable, TVal any] struct {
mtx sync.RWMutex
container map[TKey]*element[TVal]
container map[TKey]*Element[TVal]
elementPool *sync.Pool

cleanupTickTime time.Duration
Expand All @@ -22,11 +23,11 @@ type TimedMap[TKey comparable, TVal any] struct {
cleanerRunning atomic.Bool
}

// element contains the actual value as interface type,
// Element contains the actual value as interface type,
// the time when the value expires and an array of
// callbacks, which will be executed when the element
// callbacks, which will be executed when the Element
// expires.
type element[TVal any] struct {
type Element[TVal any] struct {
value TVal
expires time.Time
cbs []Callback[TVal]
Expand All @@ -35,12 +36,12 @@ type element[TVal any] struct {
// New creates and returns a new instance of TimedMap.
// The passed cleanupTickTime will be passed to the
// cleanup ticker, which iterates through the map and
// deletes expired key-value pairs.
// deletes expired key-value pairs on each iteration.
//
// Optionally, you can also pass a custom <-chan time.Time
// Optionally, you can also pass a custom <-chan time.Time,
// which controls the cleanup cycle if you want to use
// a single synchronized timer or if you want to have more
// control over the cleanup loop.
// granular control over the cleanup loop.
//
// When passing 0 as cleanupTickTime and no tickerChan,
// the cleanup loop will not be started. You can call
Expand All @@ -49,9 +50,12 @@ type element[TVal any] struct {
// can also be used to re-define the specification of
// the cleanup loop when already running if you want to.
func New[TKey comparable, TVal any](cleanupTickTime time.Duration, tickerChan ...<-chan time.Time) *TimedMap[TKey, TVal] {
return newTimedMap[TKey, TVal](make(map[TKey]*element[TVal]), cleanupTickTime, tickerChan)
return newTimedMap[TKey, TVal](make(map[TKey]*Element[TVal]), cleanupTickTime, tickerChan)
}

// FromMap creates a new TimedMap containing all entries from
// the passed map m. Each entry will get assigned the passed
// expiration duration.
func FromMap[TKey comparable, TVal any](
m map[TKey]TVal,
expiration time.Duration,
Expand All @@ -63,10 +67,10 @@ func FromMap[TKey comparable, TVal any](
}

exp := time.Now().Add(expiration)
container := make(map[TKey]*element[TVal])
container := make(map[TKey]*Element[TVal])

for k, v := range m {
el := &element[TVal]{
el := &Element[TVal]{
value: v,
expires: exp,
}
Expand Down Expand Up @@ -96,7 +100,7 @@ func (tm *TimedMap[TKey, TVal]) GetValue(key TKey) (val TVal, ok bool) {
return v.value, true
}

// GetExpires returns the expire time of a key-value pair.
// GetExpires returns the expiry time of a key-value pair.
// If the key-value pair does not exist in the map or
// was expired, this will return an error object.
func (tm *TimedMap[TKey, TVal]) GetExpires(key TKey) (time.Time, error) {
Expand All @@ -107,13 +111,7 @@ func (tm *TimedMap[TKey, TVal]) GetExpires(key TKey) (time.Time, error) {
return v.expires, nil
}

// SetExpire is deprecated.
// Please use SetExpires instead.
func (tm *TimedMap[TKey, TVal]) SetExpire(key TKey, d time.Duration) error {
return tm.SetExpires(key, d)
}

// SetExpires sets the expire time for a key-value
// SetExpires sets the expiry time for a key-value
// pair to the passed duration. If there is no value
// to the key passed , this will return an error.
func (tm *TimedMap[TKey, TVal]) SetExpires(key TKey, d time.Duration) error {
Expand All @@ -132,7 +130,7 @@ func (tm *TimedMap[TKey, TVal]) Remove(key TKey) {
tm.remove(key)
}

// Refresh extends the expire time for a key-value pair
// Refresh extends the expiry time for a key-value pair
// about the passed duration. If there is no value to
// the key passed, this will return an error object.
func (tm *TimedMap[TKey, TVal]) Refresh(key TKey, d time.Duration) error {
Expand Down Expand Up @@ -172,7 +170,7 @@ func (tm *TimedMap[TKey, TVal]) StartCleanerInternal(interval time.Duration) {
// StartCleanerExternal starts the cleanup loop controlled
// by the given initiator channel. This is useful if you
// want to have more control over the cleanup loop or if
// you want to sync up multiple timedmaps.
// you want to sync up multiple TimedMaps.
//
// If the cleanup loop is already running, it will be
// stopped and restarted using the new specification.
Expand Down Expand Up @@ -221,9 +219,9 @@ func (tm *TimedMap[TKey, TVal]) cleanupLoop(tc <-chan time.Time) {
}
}

// expireElement removes the specified key-value element
// expireElement removes the specified key-value Element
// from the map and executes all defined Callback functions
func (tm *TimedMap[TKey, TVal]) expireElement(key TKey, v *element[TVal]) {
func (tm *TimedMap[TKey, TVal]) expireElement(key TKey, v *Element[TVal]) {
for _, cb := range v.cbs {
cb(v.value)
}
Expand All @@ -232,7 +230,7 @@ func (tm *TimedMap[TKey, TVal]) expireElement(key TKey, v *element[TVal]) {
delete(tm.container, key)
}

// cleanUp iterates trhough the map and expires all key-value
// cleanUp iterates through the map and expires all key-value
// pairs which expire time after the current time
func (tm *TimedMap[TKey, TVal]) cleanUp() {
now := time.Now()
Expand All @@ -250,7 +248,7 @@ func (tm *TimedMap[TKey, TVal]) cleanUp() {
// set sets the value for a key and section with the
// given expiration parameters
func (tm *TimedMap[TKey, TVal]) set(key TKey, val TVal, expiresAfter time.Duration, cb ...Callback[TVal]) {
// re-use element when existent on this key
// re-use Element when existent on this key
if v := tm.getRaw(key); v != nil {
tm.mtx.Lock()
defer tm.mtx.Unlock()
Expand All @@ -263,16 +261,16 @@ func (tm *TimedMap[TKey, TVal]) set(key TKey, val TVal, expiresAfter time.Durati
tm.mtx.Lock()
defer tm.mtx.Unlock()

v := tm.elementPool.Get().(*element[TVal])
v := tm.elementPool.Get().(*Element[TVal])
v.value = val
v.expires = time.Now().Add(expiresAfter)
v.cbs = cb
tm.container[key] = v
}

// get returns an element object by key and section
// get returns an Element object by key and section
// if the value has not already expired
func (tm *TimedMap[TKey, TVal]) get(key TKey) *element[TVal] {
func (tm *TimedMap[TKey, TVal]) get(key TKey) *Element[TVal] {
v := tm.getRaw(key)

if v == nil {
Expand All @@ -290,9 +288,9 @@ func (tm *TimedMap[TKey, TVal]) get(key TKey) *element[TVal] {
return v
}

// getRaw returns the raw element object by key,
// getRaw returns the raw Element object by key,
// not depending on expiration time
func (tm *TimedMap[TKey, TVal]) getRaw(key TKey) *element[TVal] {
func (tm *TimedMap[TKey, TVal]) getRaw(key TKey) *Element[TVal] {
tm.mtx.RLock()
v, ok := tm.container[key]
tm.mtx.RUnlock()
Expand All @@ -304,7 +302,7 @@ func (tm *TimedMap[TKey, TVal]) getRaw(key TKey) *element[TVal] {
return v
}

// remove removes an element from the map by give back the key
// remove removes an Element from the map by give back the key
func (tm *TimedMap[TKey, TVal]) remove(key TKey) {
tm.mtx.Lock()
defer tm.mtx.Unlock()
Expand Down Expand Up @@ -358,7 +356,7 @@ func (tm *TimedMap[TKey, TVal]) getSnapshot() (m map[TKey]TVal) {
}

func newTimedMap[TKey comparable, TVal any](
container map[TKey]*element[TVal],
container map[TKey]*Element[TVal],
cleanupTickTime time.Duration,
tickerChan []<-chan time.Time,
) *TimedMap[TKey, TVal] {
Expand All @@ -367,7 +365,7 @@ func newTimedMap[TKey comparable, TVal any](
cleanerStopChan: make(chan bool),
elementPool: &sync.Pool{
New: func() any {
return new(element[TVal])
return new(Element[TVal])
},
},
}
Expand Down

0 comments on commit 1bb2fbc

Please sign in to comment.