1 回答
TA贡献1803条经验 获得超3个赞
那不是您应该测试代码性能的方式。您应该使用 Go 的内置测试框架(testing
包和go test
命令)。
让我们创建可测试代码:
func f() {
// Code that must only be run once
}
var testOnce = &sync.Once{}
func DoWithOnce() {
testOnce.Do(f)
}
var (
mu = &sync.Mutex{}
b bool
)
func DoWithMutex() {
mu.Lock()
if !b {
f()
b = true
}
mu.Unlock()
}
让我们使用该testing包编写适当的测试/基准测试代码:
func BenchmarkOnce(b *testing.B) {
for i := 0; i < b.N; i++ {
DoWithOnce()
}
}
func BenchmarkMutex(b *testing.B) {
for i := 0; i < b.N; i++ {
DoWithMutex()
}
}
我们可以使用以下代码运行基准测试:
go test -bench .
以下是基准测试结果:
BenchmarkOnce-4 200000000 6.30 ns/op
BenchmarkMutex-4 100000000 20.0 ns/op
PASS
如您所见,使用sync.Once()比使用sync.Mutex. 为什么?因为sync.Once()有一个“优化”的短路径,它只使用原子加载来检查任务之前是否被调用过,如果是,则不使用互斥锁。“慢速”路径可能只在第一次调用Once.Do(). 虽然如果你有许多并发的 goroutines 试图调用DoWithOnce(),慢速路径可能会多次到达,但从长远来看once.Do()只需要使用原子负载。
并行测试(来自多个 goroutines)
是的,上面的基准测试代码仅使用单个 goroutine 进行测试。但是使用多个并发 goroutine 只会让互斥体的情况变得更糟,因为它总是必须获得一个互斥体来检查是否要调用任务,而只sync.Once使用原子负载。
尽管如此,让我们对其进行基准测试。
以下是使用并行测试的基准测试代码:
func BenchmarkOnceParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
DoWithOnce()
}
})
}
func BenchmarkMutexParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
DoWithMutex()
}
})
}
我的机器上有 4 个内核,所以我将使用这 4 个内核:
go test -bench Parallel -cpu=4
(您可以省略该-cpu标志,在这种情况下,它默认为GOMAXPROCS– 可用核心数。)
结果如下:
BenchmarkOnceParallel-4 500000000 3.04 ns/op
BenchmarkMutexParallel-4 20000000 93.7 ns/op
当“并发增加”时,结果开始变得无与伦比sync.Once(在上面的测试中,它快了 30 倍)。
我们可能会进一步增加使用创建的 goroutines 的数量testing.B.SetPralleism(),但是当我将它设置为 100 时我得到了类似的结果(这意味着 400 个 goroutines 被用来调用基准测试代码)。
- 1 回答
- 0 关注
- 128 浏览
添加回答
举报