为什么基准测试中,非常短的 time.Sleep 操作所需时间比请求的时间(大约 300 纳秒)更长?

4

我正在尝试在Go语言中对基准测试进行实验,我有一个简单的函数,只是休眠5纳秒,但是当我运行基准测试时,它显示 298.1 ns/op。 我很好奇为什么会这样。难道不应该是5ns/op吗?

Go版本:

go version go1.19 linux/amd64

代码:

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()
    }
}

结果:

$ 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
请问您自己:您的CPU需要多少个时钟周期才能对应5纳秒?您会如何测量5纳秒? - Volker
@Volker:幸运的是,这个基准测试并不试图分别测量每个睡眠时间,但是它确实试图睡眠非常短的时间。在Ice Lake / Tiger Lake上,rdtsc吞吐量约为27个周期(https://uops.info/),因此在最大睿频4.7 GHz下,每5.7 ns左右就会发生一次。但是你可以肯定,单独的time.Sleep调用需要做的不仅仅是检查当前时间戳计数器一次! - Peter Cordes
Tiger Lake没有WAITPKG扩展来暂停CPU直到短期截止日期;这是在Tremont和Alder Lake中新增的。即使如此,它也需要rdtsc + tpause,在检查睡眠时间太短以至于不能调用系统调用让其调度另一个任务直到我们准备好唤醒之后。总之,尝试睡眠或延迟约20个时钟周期并不是高级语言中有意义的事情;你最好手写汇编代码来适应已知的微体系结构。另请参见(如何在x86 Linux上计算asm延迟循环的时间? - Peter Cordes
2个回答

5

4

文章 "如何在Go中编写准确的基准测试" (Teiva Harsanyi, 2022年8月) 和 基准测试维基页面 都提到了 perflock (在Linux上):

我们应该确保执行基准测试的机器处于空闲状态。然而,外部进程可能在后台运行,这可能会影响基准测试结果。

出于这个原因,像 perflock 这样的工具可以限制基准测试可以使用多少CPU。

例如,我们可以使用总可用CPU的70%运行基准测试,将30%分配给操作系统和其他进程,并减少机器活动因素对结果的影响。

另请参阅 "问题44343:runtime: time.Sleep花费的时间比预期更长。"

对于Linux,我们应该使用epoll_pwait2,例如https://go.dev/cl/363417
这个系统调用是比较新的,但这将改善未来的情况,并为特别受影响的用户提供一种解决方法(升级内核)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接