| /* | |
| * | |
| * Copyright 2017 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 contains benchmarks for various synchronization primitives | |
| // available in Go. | |
| package primitives_test | |
| import ( | |
| "fmt" | |
| "sync" | |
| "sync/atomic" | |
| "testing" | |
| "time" | |
| "unsafe" | |
| ) | |
| func BenchmarkSelectClosed(b *testing.B) { | |
| c := make(chan struct{}) | |
| close(c) | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| select { | |
| case <-c: | |
| x++ | |
| default: | |
| } | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkSelectOpen(b *testing.B) { | |
| c := make(chan struct{}) | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| select { | |
| case <-c: | |
| default: | |
| x++ | |
| } | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkAtomicBool(b *testing.B) { | |
| c := int32(0) | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| if atomic.LoadInt32(&c) == 0 { | |
| x++ | |
| } | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkAtomicValueLoad(b *testing.B) { | |
| c := atomic.Value{} | |
| c.Store(0) | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| if c.Load().(int) == 0 { | |
| x++ | |
| } | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkAtomicValueStore(b *testing.B) { | |
| c := atomic.Value{} | |
| v := 123 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| c.Store(v) | |
| } | |
| b.StopTimer() | |
| } | |
| func BenchmarkMutex(b *testing.B) { | |
| c := sync.Mutex{} | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| c.Lock() | |
| x++ | |
| c.Unlock() | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkRWMutex(b *testing.B) { | |
| c := sync.RWMutex{} | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| c.RLock() | |
| x++ | |
| c.RUnlock() | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkRWMutexW(b *testing.B) { | |
| c := sync.RWMutex{} | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| c.Lock() | |
| x++ | |
| c.Unlock() | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkMutexWithDefer(b *testing.B) { | |
| c := sync.Mutex{} | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| func() { | |
| c.Lock() | |
| defer c.Unlock() | |
| x++ | |
| }() | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkMutexWithClosureDefer(b *testing.B) { | |
| c := sync.Mutex{} | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| func() { | |
| c.Lock() | |
| defer func() { c.Unlock() }() | |
| x++ | |
| }() | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkMutexWithoutDefer(b *testing.B) { | |
| c := sync.Mutex{} | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| func() { | |
| c.Lock() | |
| x++ | |
| c.Unlock() | |
| }() | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkAtomicAddInt64(b *testing.B) { | |
| var c int64 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| atomic.AddInt64(&c, 1) | |
| } | |
| b.StopTimer() | |
| if c != int64(b.N) { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkAtomicTimeValueStore(b *testing.B) { | |
| var c atomic.Value | |
| t := time.Now() | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| c.Store(t) | |
| } | |
| b.StopTimer() | |
| } | |
| func BenchmarkAtomic16BValueStore(b *testing.B) { | |
| var c atomic.Value | |
| t := struct { | |
| a int64 | |
| b int64 | |
| }{ | |
| 123, 123, | |
| } | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| c.Store(t) | |
| } | |
| b.StopTimer() | |
| } | |
| func BenchmarkAtomic32BValueStore(b *testing.B) { | |
| var c atomic.Value | |
| t := struct { | |
| a int64 | |
| b int64 | |
| c int64 | |
| d int64 | |
| }{ | |
| 123, 123, 123, 123, | |
| } | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| c.Store(t) | |
| } | |
| b.StopTimer() | |
| } | |
| func BenchmarkAtomicPointerStore(b *testing.B) { | |
| t := 123 | |
| var up unsafe.Pointer | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| atomic.StorePointer(&up, unsafe.Pointer(&t)) | |
| } | |
| b.StopTimer() | |
| } | |
| func BenchmarkAtomicTimePointerStore(b *testing.B) { | |
| t := time.Now() | |
| var up unsafe.Pointer | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| atomic.StorePointer(&up, unsafe.Pointer(&t)) | |
| } | |
| b.StopTimer() | |
| } | |
| func BenchmarkStoreContentionWithAtomic(b *testing.B) { | |
| t := 123 | |
| var c unsafe.Pointer | |
| b.RunParallel(func(pb *testing.PB) { | |
| for pb.Next() { | |
| atomic.StorePointer(&c, unsafe.Pointer(&t)) | |
| } | |
| }) | |
| } | |
| func BenchmarkStoreContentionWithMutex(b *testing.B) { | |
| t := 123 | |
| var mu sync.Mutex | |
| var c int | |
| b.RunParallel(func(pb *testing.PB) { | |
| for pb.Next() { | |
| mu.Lock() | |
| c = t | |
| mu.Unlock() | |
| } | |
| }) | |
| _ = c | |
| } | |
| type dummyStruct struct { | |
| a int64 | |
| b time.Time | |
| } | |
| func BenchmarkStructStoreContention(b *testing.B) { | |
| d := dummyStruct{} | |
| dp := unsafe.Pointer(&d) | |
| t := time.Now() | |
| for _, j := range []int{100000000, 10000, 0} { | |
| for _, i := range []int{100000, 10} { | |
| b.Run(fmt.Sprintf("CAS/%v/%v", j, i), func(b *testing.B) { | |
| b.SetParallelism(i) | |
| b.RunParallel(func(pb *testing.PB) { | |
| n := &dummyStruct{ | |
| b: t, | |
| } | |
| for pb.Next() { | |
| for y := 0; y < j; y++ { | |
| } | |
| for { | |
| v := (*dummyStruct)(atomic.LoadPointer(&dp)) | |
| n.a = v.a + 1 | |
| if atomic.CompareAndSwapPointer(&dp, unsafe.Pointer(v), unsafe.Pointer(n)) { | |
| n = v | |
| break | |
| } | |
| } | |
| } | |
| }) | |
| }) | |
| } | |
| } | |
| var mu sync.Mutex | |
| for _, j := range []int{100000000, 10000, 0} { | |
| for _, i := range []int{100000, 10} { | |
| b.Run(fmt.Sprintf("Mutex/%v/%v", j, i), func(b *testing.B) { | |
| b.SetParallelism(i) | |
| b.RunParallel(func(pb *testing.PB) { | |
| for pb.Next() { | |
| for y := 0; y < j; y++ { | |
| } | |
| mu.Lock() | |
| d.a++ | |
| d.b = t | |
| mu.Unlock() | |
| } | |
| }) | |
| }) | |
| } | |
| } | |
| } | |
| type myFooer struct{} | |
| func (myFooer) Foo() {} | |
| type fooer interface { | |
| Foo() | |
| } | |
| func BenchmarkInterfaceTypeAssertion(b *testing.B) { | |
| // Call a separate function to avoid compiler optimizations. | |
| runInterfaceTypeAssertion(b, myFooer{}) | |
| } | |
| func runInterfaceTypeAssertion(b *testing.B, fer any) { | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| if _, ok := fer.(fooer); ok { | |
| x++ | |
| } | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkStructTypeAssertion(b *testing.B) { | |
| // Call a separate function to avoid compiler optimizations. | |
| runStructTypeAssertion(b, myFooer{}) | |
| } | |
| func runStructTypeAssertion(b *testing.B, fer any) { | |
| x := 0 | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| if _, ok := fer.(myFooer); ok { | |
| x++ | |
| } | |
| } | |
| b.StopTimer() | |
| if x != b.N { | |
| b.Fatal("error") | |
| } | |
| } | |
| func BenchmarkWaitGroupAddDone(b *testing.B) { | |
| wg := sync.WaitGroup{} | |
| b.RunParallel(func(pb *testing.PB) { | |
| i := 0 | |
| for ; pb.Next(); i++ { | |
| wg.Add(1) | |
| } | |
| for ; i > 0; i-- { | |
| wg.Done() | |
| } | |
| }) | |
| } | |
| func BenchmarkRLockUnlock(b *testing.B) { | |
| mu := sync.RWMutex{} | |
| b.RunParallel(func(pb *testing.PB) { | |
| i := 0 | |
| for ; pb.Next(); i++ { | |
| mu.RLock() | |
| } | |
| for ; i > 0; i-- { | |
| mu.RUnlock() | |
| } | |
| }) | |
| } | |
| type ifNop interface { | |
| nop() | |
| } | |
| type alwaysNop struct{} | |
| func (alwaysNop) nop() {} | |
| type concreteNop struct { | |
| isNop atomic.Bool | |
| i int | |
| } | |
| func (c *concreteNop) nop() { | |
| if c.isNop.Load() { | |
| return | |
| } | |
| c.i++ | |
| } | |
| func BenchmarkInterfaceNop(b *testing.B) { | |
| n := ifNop(alwaysNop{}) | |
| b.RunParallel(func(pb *testing.PB) { | |
| for pb.Next() { | |
| n.nop() | |
| } | |
| }) | |
| } | |
| func BenchmarkConcreteNop(b *testing.B) { | |
| n := &concreteNop{} | |
| n.isNop.Store(true) | |
| b.RunParallel(func(pb *testing.PB) { | |
| for pb.Next() { | |
| n.nop() | |
| } | |
| }) | |
| } |