Why does a very short time.Sleep take longer than the requested (about 300 ns) in benchmarks?

Viewed 58

I'm playing around with benchmarks in Go and I have a simple function that just sleeps for 5 Nanoseconds, however when I run a benchmark test it shows 298.1 ns/op. I'm curious why is that. Shouldn't it be 5 ns/op?

Go version:

go version go1.19 linux/amd64

The code:

package andrei

import (
    "testing"
    "time"
)

func Hi() {
    time.Sleep(5 * time.Nanosecond)
}

func BenchmarkHi(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Hi()
    }
}

The results:

$ go test -run none -bench  . -benchmem ./andrei

goos: linux
goarch: amd64
pkg: andrei/andrei
cpu: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
BenchmarkHi-8            3861392               298.1 ns/op             0 B/op          0 allocs/op
PASS
ok      andrei/andrei   1.470s
2 Answers

The article "How to Write Accurate Benchmarks in Go" (Teiva Harsanyi, Aug. 2022) and the Benchmarks wiki page both mention perflock (on Linux):

We should make sure the machine executing the benchmark is idle. However, external processes may run in the background, which may affect benchmark results.

For that reason, tools such as perflock can limit how much CPU a benchmark can consume.

For example, we can run a benchmark with 70% of the total available CPU, giving 30% to the OS and other processes and reducing the impact of the machine activity factor on the results.

See also "issues 44343: runtime: time.Sleep takes more time than expected".

For Linux, we should use epoll_pwait2, like https://go.dev/cl/363417.
This system call is on the newer side, but this will improve things going forward and provide a workaround (upgrade the kernel) for particularly affected users.

Related