我遇到了Go程序基准测试的问题,它看起来像是一个Go bug。请帮助我理解为什么不是。
简而言之:即使我之后再调用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()
}
}
提前感谢任何提示!我和谷歌以及此处都是短缺的。
编辑:似乎这只发生在FunctionOfInterest()
真的很快;是的,我特别想在我的基准测试循环中做到这一点。我已更新示例代码以使其更清晰。而我想我现在已经弄清楚了。
答案 0 :(得分:1)
TL; DR:这不是一个错误,它只是有点棘手。
我很确定我知道发生了什么。在基准测试循环中使用StopTimer
和StartTimer
不是问题;问题是我做想要测试的功能对于它自己的好处来说太快了。或者反正我的好。
通过调整该函数使用的休眠时间,我们可以非常轻松地观察基准测试差异。至少对于这么简单的事情,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处理10分钟。)
好消息当然是它不是一个bug;坏消息是它仍然非常令人讨厌,它不仅仅停留在百万次迭代上并告诉你最好的猜测。
但话又说回来,另一个好消息是遇到这个问题表明你的功能足够快,可能不需要基准测试。
剩下的谜团是:为什么这只会在我们停止并启动计时器时发生?
在没有摆弄计时器的情况下,我可以调用我的noop函数,并且只需20亿次迭代即可对其进行基准测试:
BenchmarkSomething-4 2000000000 0.33 ns/op
ok bmtimer 0.706s
我最好的猜测是基于@JimB的评论 - 停止世界需要至少一点时间,当你运行20亿次迭代时,可以轻松运行十分钟。就像那个男人说的那样:这里有10亿,那里有10亿,很快你就会谈到真钱。
(无论如何,这是我的猜测。比我更聪明的人可能会纠正我。)
因此,我们的故事的道德是:是一定要利用b.StopTimer()
和b.StartTimer()
来处理状态,如果你没有别的办法可以处理用它。但请记住,对于非常快的函数,对于基准测试程序来说可能太多了。
我希望我知道一种更好的方法来隔离快速功能的特定功能基准。我在尝试StopTimer
之前使用的初始黑客是使用另一个基准函数来对额外代码进行基准测试,因此你得到两个数字:ns / op包含所有内容,而ns / op 没有感兴趣的功能。这可行,它可能比停止计时器更准确,但它确实需要提前了解如何解释基准输出。