Skip to content

Instantly share code, notes, and snippets.

@TheHippo
Created August 15, 2015 02:22
Show Gist options
  • Save TheHippo/75babf83556dc6f638a4 to your computer and use it in GitHub Desktop.
Save TheHippo/75babf83556dc6f638a4 to your computer and use it in GitHub Desktop.
Benchmarking Go Mutex overhead
package main
import (
"fmt"
"sync"
)
type unlocked struct {
i int
}
type locked struct {
i int
l sync.Mutex
}
func (c *unlocked) add() {
c.i++
}
func (c *locked) add() {
c.l.Lock()
defer c.l.Unlock()
c.i++
}
func main() {
fmt.Println("ok")
}
package main
import "testing"
func TestLocked_add(t *testing.T) {
c := locked{}
c.add()
if c.i != 1 {
t.Errorf("Expected 1 but got %d", c.i)
}
}
func BenchmarkLocked_add(t *testing.B) {
c := locked{}
for i := 0; i < t.N; i++ {
c.add()
}
if c.i != t.N {
t.Errorf("Expected %d, but got %d", t.N, c.i)
}
}
func TestUnlocked_add(t *testing.T) {
c := unlocked{}
c.add()
if c.i != 1 {
t.Errorf("Expected 1 but got %d", c.i)
}
}
func BenchmarkUnlocked_add(t *testing.B) {
c := unlocked{}
for i := 0; i < t.N; i++ {
c.add()
}
if c.i != t.N {
t.Errorf("Expected %d, but got %d", t.N, c.i)
}
}
$ go test -v -bench .
=== RUN TestLocked_add
--- PASS: TestLocked_add (0.00s)
=== RUN TestUnlocked_add
--- PASS: TestUnlocked_add (0.00s)
PASS
BenchmarkLocked_add 10000000 143 ns/op
BenchmarkUnlocked_add 1000000000 2.41 ns/op
ok _/home/hippo/test/mutex-bench 4.226s
@pascaldekloe
Copy link

go version go1.11.2 darwin/amd64

BenchmarkLocked_add-12      	30000000	        48.2 ns/op
BenchmarkUnlocked_add-12    	2000000000	         1.55 ns/op

Most of the slowdown comes from defer. When rewritten as

func (c *locked) add() {
	c.l.Lock()
	c.i++
	c.l.Unlock()
}

… then the numbers go down to:

BenchmarkLocked_add-12      	100000000	        19.4 ns/op
BenchmarkUnlocked_add-12    	2000000000	         1.55 ns/op

@psucodervn
Copy link

Defer's performance was improved from Go 1.14 https://golang.org/doc/go1.14#runtime

go version go1.16.4 darwin/amd64

Code

func (c *locked) addWithoutDefer() {
	c.l.Lock()
	c.i++
	c.l.Unlock()
}

func (c *locked) addWithDefer() {
	c.l.Lock()
	defer c.l.Unlock()
	c.i++
}

Result

goos: darwin
goarch: amd64
pkg: defer
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkLocked_addWithoutDefer
BenchmarkLocked_addWithoutDefer-12    	96066108	        12.30 ns/op
BenchmarkLocked_addWithDefer
BenchmarkLocked_addWithDefer-12       	92623209	        12.95 ns/op
BenchmarkUnlocked_add
BenchmarkUnlocked_add-12              	891768594	         1.322 ns/op

@btrvodka
Copy link

btrvodka commented Nov 3, 2023

go1.21.3 darwin/arm64


go test -v -bench .
=== RUN   TestLocked_add
--- PASS: TestLocked_add (0.00s)
=== RUN   TestUnlocked_add
--- PASS: TestUnlocked_add (0.00s)
goos: darwin
goarch: arm64
pkg: awesomeProject
BenchmarkLocked_add
BenchmarkLocked_add-12          87840225                13.77 ns/op
BenchmarkUnlocked_add
BenchmarkUnlocked_add-12        1000000000               0.3220 ns/op
PASS
ok      awesomeProject  2.228s

@AlexanderYastrebov
Copy link

For those coming from internet: this benchmarks nothing fastpath that is atomic CAS because benchmark does not access mutex in parallel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment