性能随机下降

时间:2017-09-02 08:49:10

标签: windows performance go benchmarking

我是Go的新手,最近有些事让我很困惑。

我有一段代码(下面发布的简化版本),我试图测量它的性能。我这样做有两种方式:1)带有测试包的bencmark 2)手动记录时间

运行基准测试输出结果

30000 55603 ns/op

这很好,但是...当我执行相同函数的30k运行记录每次迭代的时间时,我得到这样的输出:

test took 0 ns test took 0 ns ... ~10 records all the same test took 1000100 ns test took 0 ns test took 0 ns ... lots of zeroes again test took 0 ns test took 1000000 ns test took 0 ns ...

进行数学运算表明平均值确实是55603 ns/op,正如基准声明的那样。

好吧,我说,我在优化性能方面并不是那么好,而不是所有的核心编译器,但我想这可能是随机的垃圾收集?所以我打开gc日志,确保显示一些输出,然后关闭gc以获得良好的aaand ...没有垃圾收集,但我看到相同的图片 - 一些迭代需要花费一百万倍(?)。

我对这一切的理解是错误的99%,也许有人可以指出我正确的方向或者也许有人确切知道到底是怎么回事? :)

P.S。另外,对我而言,纳秒(0 ns)有点令人惊讶,这似乎太快了,但程序确实提供了计算结果,所以我不知道该怎么想。 T_T

编辑1:回答Kenny Grant的问题:我正在使用goroutine来实现值的生成排序,以便有懒惰,现在我删除了它们并简化了代码。现在这个问题要少得多,但它仍然可以重现。 游乐场链接:https://play.golang.org/p/UQMgtT4Jrf 有趣的是,这不会发生在操场上,但仍然会发生在我的机器上。

编辑2:我在win7 x64上运行Go 1.9

编辑3:感谢回复,我现在知道这个代码在游乐场上无法正常工作。我会在此处重新发布代码段,以便我们不会松开它。 :)

type PrefType string
var types []PrefType = []PrefType{
    "TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5", "TYPE6",
}

func GetKeys(key string) []string {
    var result []string
    for _, t := range types {
        rr := doCalculations(t)
        for _, k := range rr {
            result = append(result, key + "." + k)
        }
    }
    return result
}

func doCalculations(prefType PrefType) []string {
    return []string{ string(prefType) + "something", string(prefType) + "else" }
}

func test() {
    start := time.Now()
    keysPrioritized := GetKeys("spec_key")
    for _, k := range keysPrioritized {
        _ = fmt.Sprint(k)
    }
    fmt.Printf("test took %v ns\n", time.Since(start).Nanoseconds())
}

func main() {
    for i := 0; i < 30000; i++  {
        test()
    }
}

这是我机器上的输出:

enter image description here

编辑4:我在Ubuntu 17.04的笔记本电脑上尝试过相同的操作,输出合理,没有零和数百万。看起来像编译器/运行时库中的Windows特定问题。如果有人可以在他们的机器上验证这一点会很棒(Win 7/8/10)。

2 个答案:

答案 0 :(得分:2)

在Windows上,如此短的时间内,您没有足够精确的时间戳。 Linux有更精确的时间戳。根据设计,Go基准测试运行至少一秒钟。 Go1.9 +使用单调(m)值来计算持续时间。

在Windows上:

timedur.go

package main

import (
    "fmt"
    "os"
    "time"
)

type PrefType string

var types []PrefType = []PrefType{
    "TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5", "TYPE6",
}

func GetKeys(key string) []string {
    var result []string
    for _, t := range types {
        rr := doCalculations(t)
        for _, k := range rr {
            result = append(result, key+"."+k)
        }
    }
    return result
}

func doCalculations(prefType PrefType) []string {
    return []string{string(prefType) + "something", string(prefType) + "else"}
}

func test() {
    start := time.Now()
    keysPrioritized := GetKeys("spec_key")
    for _, k := range keysPrioritized {
        _ = fmt.Sprint(k)
    }
    end := time.Now()
    fmt.Printf("test took %v ns\n", time.Since(start).Nanoseconds())
    fmt.Println(start)
    fmt.Println(end)
    if end.Sub(start) < time.Microsecond {
        os.Exit(1)
    }
}

func main() {
    for i := 0; i < 30000; i++ {
        test()
    }
}

输出:

>go run timedur.go
test took 1026000 ns
2017-09-02 14:21:58.1488675 -0700 PDT m=+0.010003700
2017-09-02 14:21:58.1498935 -0700 PDT m=+0.011029700
test took 0 ns
2017-09-02 14:21:58.1538658 -0700 PDT m=+0.015002000
2017-09-02 14:21:58.1538658 -0700 PDT m=+0.015002000
exit status 1
>

在Linux上:

输出:

$ go run timedur.go
test took 113641 ns
2017-09-02 14:52:02.917175333 +0000 UTC m=+0.001041249
2017-09-02 14:52:02.917287569 +0000 UTC m=+0.001153717
test took 23614 ns
2017-09-02 14:52:02.917600301 +0000 UTC m=+0.001466208
2017-09-02 14:52:02.917623585 +0000 UTC m=+0.001489354
test took 22814 ns
2017-09-02 14:52:02.917726364 +0000 UTC m=+0.001592236
2017-09-02 14:52:02.917748805 +0000 UTC m=+0.001614575
test took 21139 ns
2017-09-02 14:52:02.917818409 +0000 UTC m=+0.001684292
2017-09-02 14:52:02.917839184 +0000 UTC m=+0.001704954
test took 21478 ns
2017-09-02 14:52:02.917911899 +0000 UTC m=+0.001777712
2017-09-02 14:52:02.917932944 +0000 UTC m=+0.001798712
test took 31032 ns
<SNIP>

结果具有可比性。它们运行在同一台机器上,一台带有Windows 10和Ubuntu 16.04的双启动。

答案 1 :(得分:1)

最好消除GC,因为显然记录它会干扰时间。操场上的时间是假的,所以这在那里不起作用。在本地试用它,我提供的代码没有0 ns的时间,看起来它按预期工作。

你当然应该期待一些时间的变化 - 当我尝试时,结果都在同一数量级内(非常小的时间为0.000003779秒),但即使你做了30次运行,偶尔会有一些昙花一现高达两倍 - 但在此分辨率下运行时间不太可能给你可靠的结果,因为它取决于计算机上运行的其他内容,内存布局等。更好地尝试以这种方式运行长时间运行而不是像这样的非常短的时间一个并且计时大量的操作并对它们进行平均 - 这就是为什么基准测试工具可以为您提供平均超过这么多次运行的原因。

由于时间仅适用于花费很少时间的操作,并且没有太大的不同,我认为这是提供代码的正常行为。 0ns结果是错误的,但可能是您之前使用goroutine的结果,很难判断没有代码,因为您提供的代码不会给出结果。