From cffbf44e851f1a2068ae6273eb8795031c3b6fad Mon Sep 17 00:00:00 2001 From: Tyler Bunnell Date: Mon, 19 May 2014 20:55:34 -0600 Subject: [PATCH 1/5] Add mutex benchmark --- context_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/context_test.go b/context_test.go index e1dca3b..36975d7 100644 --- a/context_test.go +++ b/context_test.go @@ -85,3 +85,52 @@ func TestContext(t *testing.T) { Clear(r) assertEqual(len(data), 0) } + +func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) { + <-wait + for i := 0; i < iterations; i++ { + Get(r, key) + } + done <- struct{}{} + +} + +func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) { + <-wait + for i := 0; i < iterations; i++ { + Get(r, key) + } + done <- struct{}{} + +} + +func BenchmarkMutex(b *testing.B) { + + b.StopTimer() + r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + done := make(chan struct{}) + numWriters := 64 + numReaders := numWriters * 8 + iterations := 128 + b.StartTimer() + + for i := 0; i < b.N; i++ { + wait := make(chan struct{}) + + for i := 0; i < numReaders; i++ { + go parallelReader(r, "test", iterations, wait, done) + } + + for i := 0; i < numWriters; i++ { + go parallelWriter(r, "test", "123", iterations, wait, done) + } + + close(wait) + + for i := 0; i < numReaders+numWriters; i++ { + <-done + } + + } + +} From dad32f953222ceaeacbcb3f9354e397118a679b1 Mon Sep 17 00:00:00 2001 From: Tyler Bunnell Date: Mon, 19 May 2014 21:00:24 -0600 Subject: [PATCH 2/5] Switch to RWMutex --- context.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index bcc3faa..3c55c5f 100644 --- a/context.go +++ b/context.go @@ -11,7 +11,7 @@ import ( ) var ( - mutex sync.Mutex + mutex sync.RWMutex data = make(map[*http.Request]map[interface{}]interface{}) datat = make(map[*http.Request]int64) ) @@ -29,8 +29,8 @@ func Set(r *http.Request, key, val interface{}) { // Get returns a value stored for a given key in a given request. func Get(r *http.Request, key interface{}) interface{} { - mutex.Lock() - defer mutex.Unlock() + mutex.RLock() + defer mutex.RUnlock() if data[r] != nil { return data[r][key] } @@ -39,8 +39,8 @@ func Get(r *http.Request, key interface{}) interface{} { // GetOk returns stored value and presence state like multi-value return of map access. func GetOk(r *http.Request, key interface{}) (interface{}, bool) { - mutex.Lock() - defer mutex.Unlock() + mutex.RLock() + defer mutex.RUnlock() if _, ok := data[r]; ok { value, ok := data[r][key] return value, ok @@ -50,8 +50,8 @@ func GetOk(r *http.Request, key interface{}) (interface{}, bool) { // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. func GetAll(r *http.Request) map[interface{}]interface{} { - mutex.Lock() - defer mutex.Unlock() + mutex.RLock() + defer mutex.RUnlock() if context, ok := data[r]; ok { return context @@ -62,8 +62,8 @@ func GetAll(r *http.Request) map[interface{}]interface{} { // GetAllOk returns all stored values for the request as a map. It returns not // ok if the request was never registered. func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { - mutex.Lock() - defer mutex.Unlock() + mutex.RLock() + defer mutex.RUnlock() context, ok := data[r] return context, ok From 6e5f0a5765dc2423d064383f33ccb9306267deaf Mon Sep 17 00:00:00 2001 From: Tyler Bunnell Date: Mon, 19 May 2014 21:02:29 -0600 Subject: [PATCH 3/5] Remove defer defer has a significant overhead. --- context.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/context.go b/context.go index 3c55c5f..a7f7d85 100644 --- a/context.go +++ b/context.go @@ -19,43 +19,45 @@ var ( // Set stores a value for a given key in a given request. func Set(r *http.Request, key, val interface{}) { mutex.Lock() - defer mutex.Unlock() if data[r] == nil { data[r] = make(map[interface{}]interface{}) datat[r] = time.Now().Unix() } data[r][key] = val + mutex.Unlock() } // Get returns a value stored for a given key in a given request. func Get(r *http.Request, key interface{}) interface{} { mutex.RLock() - defer mutex.RUnlock() if data[r] != nil { + mutex.RUnlock() return data[r][key] } + mutex.RUnlock() return nil } // GetOk returns stored value and presence state like multi-value return of map access. func GetOk(r *http.Request, key interface{}) (interface{}, bool) { mutex.RLock() - defer mutex.RUnlock() if _, ok := data[r]; ok { value, ok := data[r][key] + mutex.RUnlock() return value, ok } + mutex.RUnlock() return nil, false } // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. func GetAll(r *http.Request) map[interface{}]interface{} { mutex.RLock() - defer mutex.RUnlock() - if context, ok := data[r]; ok { + mutex.RUnlock() return context } + mutex.RUnlock() return nil } @@ -63,19 +65,18 @@ func GetAll(r *http.Request) map[interface{}]interface{} { // ok if the request was never registered. func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { mutex.RLock() - defer mutex.RUnlock() - context, ok := data[r] + mutex.RUnlock() return context, ok } // Delete removes a value stored for a given key in a given request. func Delete(r *http.Request, key interface{}) { mutex.Lock() - defer mutex.Unlock() if data[r] != nil { delete(data[r], key) } + mutex.Unlock() } // Clear removes all values stored for a given request. @@ -84,8 +85,8 @@ func Delete(r *http.Request, key interface{}) { // variables at the end of a request lifetime. See ClearHandler(). func Clear(r *http.Request) { mutex.Lock() - defer mutex.Unlock() clear(r) + mutex.Unlock() } // clear is Clear without the lock. @@ -105,7 +106,6 @@ func clear(r *http.Request) { // periodically until the problem is fixed. func Purge(maxAge int) int { mutex.Lock() - defer mutex.Unlock() count := 0 if maxAge <= 0 { count = len(data) @@ -120,6 +120,7 @@ func Purge(maxAge int) int { } } } + mutex.Unlock() return count } From ee79a2c21bb78dd6d095137ddea5862ff15439e5 Mon Sep 17 00:00:00 2001 From: Tyler Bunnell Date: Mon, 19 May 2014 21:04:23 -0600 Subject: [PATCH 4/5] Fix golint warning --- doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc.go b/doc.go index 2976064..73c7400 100644 --- a/doc.go +++ b/doc.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. /* -Package gorilla/context stores values shared during a request lifetime. +Package context stores values shared during a request lifetime. For example, a router can set variables extracted from the URL and later application handlers can access those values, or it can be used to store From 13ce75261f2d4eda931f3de7b8c14dc7a56843a3 Mon Sep 17 00:00:00 2001 From: Tyler Bunnell Date: Tue, 20 May 2014 08:28:38 -0600 Subject: [PATCH 5/5] More comprehensive benchmarking --- context_test.go | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index 36975d7..6ada8ec 100644 --- a/context_test.go +++ b/context_test.go @@ -104,14 +104,11 @@ func parallelWriter(r *http.Request, key, value string, iterations int, wait, do } -func BenchmarkMutex(b *testing.B) { +func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) { b.StopTimer() r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) done := make(chan struct{}) - numWriters := 64 - numReaders := numWriters * 8 - iterations := 128 b.StartTimer() for i := 0; i < b.N; i++ { @@ -134,3 +131,31 @@ func BenchmarkMutex(b *testing.B) { } } + +func BenchmarkMutexSameReadWrite1(b *testing.B) { + benchmarkMutex(b, 1, 1, 32) +} +func BenchmarkMutexSameReadWrite2(b *testing.B) { + benchmarkMutex(b, 2, 2, 32) +} +func BenchmarkMutexSameReadWrite4(b *testing.B) { + benchmarkMutex(b, 4, 4, 32) +} +func BenchmarkMutex1(b *testing.B) { + benchmarkMutex(b, 2, 8, 32) +} +func BenchmarkMutex2(b *testing.B) { + benchmarkMutex(b, 16, 4, 64) +} +func BenchmarkMutex3(b *testing.B) { + benchmarkMutex(b, 1, 2, 128) +} +func BenchmarkMutex4(b *testing.B) { + benchmarkMutex(b, 128, 32, 256) +} +func BenchmarkMutex5(b *testing.B) { + benchmarkMutex(b, 1024, 2048, 64) +} +func BenchmarkMutex6(b *testing.B) { + benchmarkMutex(b, 2048, 1024, 512) +}