Writing contention benchmarks involves some boilerplate:
https://golang.org/cl/46010043/diff/60001/src/pkg/sync/pool_test.go
https://golang.org/cl/49910043/
etc
The general form is:
const CallsPerSched = 1000
procs := runtime.GOMAXPROCS(-1)
N := int32(b.N / CallsPerSched)
c := make(chan bool, procs)
for p := 0; p < procs; p++ {
go func() {
var buf bytes.Buffer
for atomic.AddInt32(&N, -1) >= 0 {
for g := 0; g < CallsPerSched; g++ {
f(&buf)
}
}
c <- true
}()
}
for p := 0; p < procs; p++ {
<-c
}
But sometimes:
n0 := uintptr(b.N)
atomic.AddUintptr(&n, ^uintptr(0)) < n0 {
The testing package seems to cap b.N at 2*1e9, but that's not publicly documented as a
guarantee.
Can/should we say that b.N will always fit in an int32, even if it's of type int?
I once even defensively wrote,
func BenchmarkPool(b *testing.B) {
procs := runtime.GOMAXPROCS(-1)
var dec func() bool
if unsafe.Sizeof(b.N) == 8 {
n := int64(b.N)
dec = func() bool {
return atomic.AddInt64(&n, -1) >= 0
}
} else {
n := int32(b.N)
dec = func() bool {
return atomic.AddInt32(&n, -1) >= 0
}
}
var p Pool
var wg WaitGroup
for i := 0; i < procs; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for dec() {
p.Put(1)
p.Get()
}
}()
}
wg.Wait()
}
... but felt gross about it.
We should either document this, or provide a means in the testing package to ease
writing benchmarks for contention.