Golang 基准测试 b.StopTimer() 挂起——是我吗?

Kev*_*ost 7 go

我在对 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

预先感谢您的任何提示!我在谷歌和这里都遇到了不足。

编辑:看来这只发生的速度FunctionOfInterest()非常快;是的,我特别想在我的基准循环中执行此操作。我更新了示例代码以使其更加清晰。我我现在已经明白了。

Kev*_*ost 6

TL;DR:这不是一个错误,只是有点棘手。

我很确定我知道发生了什么事。在基准测试循环中使用StopTimerStartTimer不是问题;问题是我想要测试的功能太快了。或者无论如何都是为了我好。

通过调整该函数使用的睡眠时间,我们可以很容易地观察基准测试差异。至少对于这么简单的事情,Go 运行的迭代次数越多,测量的代码返回的速度越快。这是众所周知的行为,我应该想到它。发现输出如下:

PASS
BenchmarkSomething-4     1000000          2079 ns/op
ok      bmtimer 36.587s
Run Code Online (Sandbox Code Playgroud)

FunctionOfInterest返回非常快时——或者当然是“无限”快,就像这样func tooFast() {}——那么 Go 就会继续运行更多迭代,试图获得可靠的基准。你可能会失去耐心。或者,您的计算机可能:

*** Test killed with quit: ran too long (10m0s).
FAIL    bmtimer 600.037s
Run Code Online (Sandbox Code Playgroud)

(在一个核心上 100% CPU 的情况下,这需要 10 分钟。)

当然,好消息是这不是一个错误;而是一个错误。坏消息是它仍然很烦人,因为它不只是停止在一百万次迭代并告诉你它的最佳猜测。

但话又说回来,另一个好消息是,遇到这个问题表明你的函数足够快,可能不需要基准测试。

剩下的谜团是:为什么只有当我们停止并启动计时器时才会发生这种情况?

无需摆弄计时器,我就可以调用我的 noop 函数,并且它只需 20 亿次迭代就可以很好地对其进行基准测试:

BenchmarkSomething-4    2000000000           0.33 ns/op
ok      bmtimer 0.706s
Run Code Online (Sandbox Code Playgroud)

我最好的猜测是基于@JimB 的评论——阻止世界至少需要一点时间,而当你运行 20 亿次迭代时,很容易运行超过十分钟。就像那个人说的:这里十亿,那里十亿,很快你就会谈论真正的钱。

(无论如何,这是我的猜测。比我聪明的人可能会纠正我。)

因此,我们故事的寓意是:是的,如果你没有其他方法来处理状态,那么无论如何都要利用b.StopTimer()和来处理状态。b.StartTimer()但请记住,对于非常快的函数,这对于基准测试人员来说可能太多了。

我确实希望我知道一种更好的方法来隔离快速函数的特定函数基准。我在尝试之前使用的最初黑客方法StopTimer是使用另一个基准函数来对额外的代码进行基准测试,因此您会得到两个数字:包含所有内容的 ns/op 和不包含感兴趣函数的 ns/op。这是可行的,而且可能比停止计时器更准确,但它确实需要预先了解如何解释基准输出。