性能随机下降
Random drop in performance
我是 Go 的新手,最近有一些事情让我很困惑。
我有一段代码(下面发布的简化版本),我试图测量它的性能。我用两种方式做到了这一点:1)带有测试包的基准 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
的问题:我使用 goroutines 来实现值的排序生成器以具有惰性,现在我删除了它们并简化了代码。这个问题现在不那么频繁了,但它仍然可以重现。
游乐场 link:https://play.golang.org/p/UQMgtT4Jrf
有趣的是,这不会发生在操场上,但仍然会发生在我的机器上。
编辑 2:我是 运行 在 win7 x64 上运行 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()
}
}
这是我机器上的输出:
编辑 4:我在笔记本电脑上使用 Ubuntu 17.04 进行了相同的尝试,输出是合理的,没有零和数百万。似乎是 compiler/runtime 库中的 Windows 特定问题。如果有人可以在他们的机器上验证这一点(Win 7/8/10),那就太好了。
最好消除 GC,因为显然记录它会干扰计时。操场上的时间 pkg 是假的,所以这在那里行不通。在本地尝试使用您提供的代码,我没有得到 0 ns 的时间,看起来它正在按预期工作。
你当然应该期待时间上的一些变化——当我尝试它时,结果都在同一个数量级内(非常小的时间 0.000003779 秒),但即使你运行 30 次,偶尔也会出现短暂的波动,有时会增加一倍 - 但是 运行 在此分辨率下的计时不太可能为您提供可靠的结果,因为它取决于计算机上 运行 的其他内容,内存布局等。最好尝试长时间 运行 以这种方式进行操作,而不是像这样的非常短的时间,并计算大量操作的时间并对其进行平均 - 这就是基准测试工具为您提供这么多次运行的平均值的原因。
由于操作的时间安排非常短,而且差别不大,我认为这是所提供代码的正常行为。 0ns 结果是错误的,但可能是您之前使用 goroutines 的结果,没有代码很难判断,因为您提供的代码不会给出该结果。
在 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.
我是 Go 的新手,最近有一些事情让我很困惑。
我有一段代码(下面发布的简化版本),我试图测量它的性能。我用两种方式做到了这一点:1)带有测试包的基准 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
的问题:我使用 goroutines 来实现值的排序生成器以具有惰性,现在我删除了它们并简化了代码。这个问题现在不那么频繁了,但它仍然可以重现。
游乐场 link:https://play.golang.org/p/UQMgtT4Jrf
有趣的是,这不会发生在操场上,但仍然会发生在我的机器上。
编辑 2:我是 运行 在 win7 x64 上运行 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()
}
}
这是我机器上的输出:
编辑 4:我在笔记本电脑上使用 Ubuntu 17.04 进行了相同的尝试,输出是合理的,没有零和数百万。似乎是 compiler/runtime 库中的 Windows 特定问题。如果有人可以在他们的机器上验证这一点(Win 7/8/10),那就太好了。
最好消除 GC,因为显然记录它会干扰计时。操场上的时间 pkg 是假的,所以这在那里行不通。在本地尝试使用您提供的代码,我没有得到 0 ns 的时间,看起来它正在按预期工作。
你当然应该期待时间上的一些变化——当我尝试它时,结果都在同一个数量级内(非常小的时间 0.000003779 秒),但即使你运行 30 次,偶尔也会出现短暂的波动,有时会增加一倍 - 但是 运行 在此分辨率下的计时不太可能为您提供可靠的结果,因为它取决于计算机上 运行 的其他内容,内存布局等。最好尝试长时间 运行 以这种方式进行操作,而不是像这样的非常短的时间,并计算大量操作的时间并对其进行平均 - 这就是基准测试工具为您提供这么多次运行的平均值的原因。
由于操作的时间安排非常短,而且差别不大,我认为这是所提供代码的正常行为。 0ns 结果是错误的,但可能是您之前使用 goroutines 的结果,没有代码很难判断,因为您提供的代码不会给出该结果。
在 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.