我在对我的 Go 程序进行基准测试时遇到了问题,在我看来它像是一个 Go 错误。请帮助我理解为什么不是。
简而言之:当我调用 b.StopTimer()
时基准测试挂起,即使我随后调用了 b.StartTimer()
。 AFAICT 我正在按预期使用这些功能。
这是在 Mac OSX 10.11.5“El Capitan”上,go version go1.6.1 darwin/amd64
;其他一切似乎都很好。我将在今天晚些时候尝试在 Linux 上检查并更新问题。
这是代码。像往常一样使用 go test -bench=.
运行。
package bmtimer
import (
"testing"
"time"
)
// These appear to not matter; they can be very short.
var COSTLY_PREP_DELAY = time.Nanosecond
var RESET_STATE_DELAY = time.Nanosecond
// This however -- the measured function delay -- must not be too short.
// The shorter it is, the more iterations will be timed. If it's infinitely
// short then you will hang -- or perhaps *appear* to hang?
var FUNCTION_OF_INTEREST_DELAY = time.Microsecond
func CostlyPrep() { time.Sleep(COSTLY_PREP_DELAY) }
func ResetState() { time.Sleep(RESET_STATE_DELAY) }
func FunctionOfInterest() { time.Sleep(FUNCTION_OF_INTEREST_DELAY) }
func TestPlaceHolder(t *testing.T) {}
func BenchmarkSomething(b *testing.B) {
CostlyPrep()
b.ResetTimer()
for n := 0; n < b.N; n++ {
FunctionOfInterest()
b.StopTimer()
ResetState()
b.StartTimer()
}
}
提前感谢任何提示!我在 Google 和此处都表现不佳。
编辑: 看来这只发生在 FunctionOfInterest()
非常快;是的,我特别想在基准测试循环中执行此操作。我更新了示例代码以使其更加清晰。我认为我现在想通了。
最佳答案
TL;DR:这不是错误,只是有点棘手。
我很确定我知道发生了什么。在基准测试循环中使用 StopTimer
和 StartTimer
不是问题;问题是我做想测试的功能太快了。或者无论如何都是为了我好。
通过调整该函数使用的休眠时间,我们可以很容易地观察到基准测试差异。至少对于这么简单的事情,measured 代码返回的速度越快,Go 运行的迭代越多。这是已知行为,我早该想到。一个人发现这样的输出:
PASS
BenchmarkSomething-4 1000000 2079 ns/op
ok bmtimer 36.587s
当 FunctionOfInterest
返回得非常快——或者当然是“无限”快,如在 func tooFast() {}
中——然后 Go 会继续运行更多迭代它,试图获得一个可靠的基准。你可能会失去耐心。或者,您的计算机可能:
*** Test killed with quit: ran too long (10m0s).
FAIL bmtimer 600.037s
(这是在一个内核上 100% CPU 的情况下十分钟。)
当然,好消息是这不是错误;坏消息是,它不只是停留在例如一百万次迭代并告诉您它的最佳猜测,这仍然很烦人。
不过话又说回来,另一个好消息是遇到这个问题表明你的函数足够快,可能不需要基准测试。
剩下的谜团是:为什么只有当我们停止和启动计时器时才会发生这种情况?
无需摆弄计时器,我可以调用我的 noop 函数,它可以很好地对其进行基准测试,仅需 20 亿次迭代:
BenchmarkSomething-4 2000000000 0.33 ns/op
ok bmtimer 0.706s
我最好的猜测是基于@JimB 的评论——停止世界至少需要一点时间,而当您运行 20 亿次迭代时,很容易就会超过 10 分钟。就像那个人说的:这里有 10 亿,那里有 10 亿,很快你就会谈论真正的钱。
(无论如何这是我的猜测。比我聪明的人可能会纠正我。)
因此我们故事的寓意是:是一定要利用b.StopTimer()
和b.StartTimer()
以便在您没有其他方法处理状态时处理状态。但请记住,对于非常快的函数,对于基准测试程序来说可能太多了。
我真希望我知道一种更好的方法来隔离快速函数的特定函数基准。我在尝试 StopTimer
之前使用的最初 hack 是有另一个基准函数,它只对额外的代码进行基准测试,所以你会得到两个数字:ns/op 和 ns/op 没有感兴趣的功能。这行得通,而且可能比停止计时器更准确,但它确实需要提前了解如何解释基准测试输出。
关于Golang 基准测试 b.StopTimer() 挂起——是我吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37620251/