/* * * Copyright 2019 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package primitives_test import ( "sync" "sync/atomic" "testing" ) type incrementUint64Map interface { increment(string) result(string) uint64 } type mapWithLock struct { mu sync.Mutex m map[string]uint64 } func newMapWithLock() incrementUint64Map { return &mapWithLock{ m: make(map[string]uint64), } } func (mwl *mapWithLock) increment(c string) { mwl.mu.Lock() mwl.m[c]++ mwl.mu.Unlock() } func (mwl *mapWithLock) result(c string) uint64 { return mwl.m[c] } type mapWithAtomicFastpath struct { mu sync.RWMutex m map[string]*uint64 } func newMapWithAtomicFastpath() incrementUint64Map { return &mapWithAtomicFastpath{ m: make(map[string]*uint64), } } func (mwaf *mapWithAtomicFastpath) increment(c string) { mwaf.mu.RLock() if p, ok := mwaf.m[c]; ok { atomic.AddUint64(p, 1) mwaf.mu.RUnlock() return } mwaf.mu.RUnlock() mwaf.mu.Lock() if p, ok := mwaf.m[c]; ok { atomic.AddUint64(p, 1) mwaf.mu.Unlock() return } var temp uint64 = 1 mwaf.m[c] = &temp mwaf.mu.Unlock() } func (mwaf *mapWithAtomicFastpath) result(c string) uint64 { return atomic.LoadUint64(mwaf.m[c]) } type mapWithSyncMap struct { m sync.Map } func newMapWithSyncMap() incrementUint64Map { return &mapWithSyncMap{} } func (mwsm *mapWithSyncMap) increment(c string) { p, ok := mwsm.m.Load(c) if !ok { tp := new(uint64) p, _ = mwsm.m.LoadOrStore(c, tp) } atomic.AddUint64(p.(*uint64), 1) } func (mwsm *mapWithSyncMap) result(c string) uint64 { p, _ := mwsm.m.Load(c) return atomic.LoadUint64(p.(*uint64)) } func benchmarkIncrementUint64Map(b *testing.B, f func() incrementUint64Map) { const cat = "cat" benches := []struct { name string goroutineCount int }{ { name: " 1", goroutineCount: 1, }, { name: " 10", goroutineCount: 10, }, { name: " 100", goroutineCount: 100, }, { name: "1000", goroutineCount: 1000, }, } for _, bb := range benches { b.Run(bb.name, func(b *testing.B) { m := f() var wg sync.WaitGroup wg.Add(bb.goroutineCount) b.ResetTimer() for i := 0; i < bb.goroutineCount; i++ { go func() { for j := 0; j < b.N; j++ { m.increment(cat) } wg.Done() }() } wg.Wait() b.StopTimer() if m.result(cat) != uint64(bb.goroutineCount*b.N) { b.Fatalf("result is %d, want %d", m.result(cat), b.N) } }) } } func BenchmarkMapWithSyncMutexContetion(b *testing.B) { benchmarkIncrementUint64Map(b, newMapWithLock) } func BenchmarkMapWithAtomicFastpath(b *testing.B) { benchmarkIncrementUint64Map(b, newMapWithAtomicFastpath) } func BenchmarkMapWithSyncMap(b *testing.B) { benchmarkIncrementUint64Map(b, newMapWithSyncMap) }