-
Notifications
You must be signed in to change notification settings - Fork 874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GetWithExpirationUpdate - atomic implementation #126
GetWithExpirationUpdate - atomic implementation #126
Conversation
…-new-map-design GetWithExpirationUpdate method and items are converted to pointers
I just did a quick test, but it looks like this change is quite a big performance regression:
The pointers also make the GC work harder, so there's probably some indirect performance regression there too (which may already be accounted for in the above benchmarks, didn't look in-depth). A simpler way to fix this would be to just lock everything for the entire function instead of switching from |
Hi again, I removed the |
I included new benchmarks at the bottom, as you can see it doesn't make that much of a difference. The basic issue is that locks are not free, for example take this simple benchmark: // BenchmarkMain-2 46259625 24.3 ns/op
func BenchmarkMain(b *testing.B) {
var s struct{ sync.Mutex }
for n := 0; n < b.N; n++ {
s.Lock()
s.Unlock()
}
} So that's the minimum amount of performance regression that can be expected for operations that add a new lock. The other issue is that pointers are not free either; I included a benchmark below which changes just For reference, here's what I did in my fork: // Touch replaces the expiry of a key and returns the current value.
func (c *cache) Touch(k string, d time.Duration) (interface{}, bool) {
if d == DefaultExpiration {
d = c.defaultExpiration
}
c.mu.Lock()
defer c.mu.Unlock()
item, ok := c.items[k]
if !ok {
return nil, false
}
item.Expiration = time.Now().Add(d).UnixNano()
c.items[k] = item
return item.Object, true
} And the benchmark for this compared to your version:
This is a simple benchmark of course; I tried to make a benchmark which made your version look better in cases where you're also doing other stuff in goroutines, for example: // BenchmarkTouch-2 6014732 200 ns/op My Touch
// BenchmarkTouch-2 4213928 284 ns/op Your GetWithExpirationUpdate
func BenchmarkTouch(b *testing.B) {
tc := New(DefaultExpiration, 0)
d := 5 * time.Second
tc.Set("foo", 0, DefaultExpiration)
go func() {
for {
tc.Get("foo")
time.Sleep(1 * time.Millisecond)
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
tc.GetWithExpirationUpdate("foo", d)
}
} I tried a bunch of different variations of this, but I couldn't come up with anything where your version had better performance. I don't know ... maybe I missed something – benchmarking realistic workloads of hard – but from what I can see just doing For your latest commit, here's the comparison with the original PR:
And here's the comparison with master:
master vs. just changing
|
Thank you for your long, detailed response. I just had time to do benchmarks myself and you're right, it is obvious that using a single mutex is faster. Converting the map value type to pointer was also a bad idea and cause regression. So, I will switch to your version of Thanks again and have a good day! |
You're welcome @paddlesteamer; I mostly just wanted to know this for my own reasons :-) I put up the fork over here the other day by the way: https://github.com/zgoat/zcache – I'm not 100% decided yet on some things I added so I didn't release a v1 yet, but all the existing stuff should be compatible, FYI :-) |
This PR is fixed version of #96. Main changes are:
cache.items
are converted frommap[string]Item
tomap[string]*Item
. I needed to do it because, inGetWithExpirationUpdate
, it is the only way to modify theExpiration
field of anItem
. The other way around (re-setting the item) needs a write lock, therefore blocks all reads/writes toitems
. Not convenient for 'cache-get's.Item
has its ownRWLock
. This way, we don't need a write lock inGetWithExpirationUpdate
.Supersedes #125