当我基准修改其输入的函数时,我必须为基准的每个循环复制测试数据,并在这样做时暂停计时器。这可能意味着,如果我运行go test -bench MyTest -benchtime 1s
,测试可能需要2分钟而不是1秒钟。
我做错了什么,还是我只需要忍受?
更多上下文:
我正在编写一个用于读取系统日志日志的程序。我记录范式的一部分是记录消息的第一行包含可读文本,并且以下行包含"额外信息",例如堆栈跟踪。因此,我的日志读取器(除其他外)将消息分配在第一行休息中,rsyslog逃到了#012
。
这是为此的代码:
// Splits the main line from extra information
func splitMessageExtra(line *string) string {
var prev rune
for i, char := range *line {
if prev == 0 && char == '#' {
prev = char
continue
}
if prev == '#' && char == '0' {
prev = char
continue
}
if prev == '0' && char == '1' {
prev = char
continue
}
if prev == '1' && char == '2' {
extra := (*line)[i+1:]
*line = (*line)[0 : i-3]
return extra
}
prev = 0
}
return ""
}
它最初使用strings.Split
并返回了新字符串,但是CPU分析表明它太慢了。
这是基准函数:
var testMessage = `Feb 10 15:16:20 foo_stats[-] (warning): [foo_stats.postfix, line 166, thread "processor_mta03"]: Skipped line because there is no context:#012Feb 10 15:16:20 mta03 postfix/qmgr[7419]: ABCDEF123: from=<>, size=24431, nrcpt=1 (queue active)`
func BenchmarkSplitMessageExtra(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
msg := string([]byte(testMessage))
b.StartTimer()
splitMessageExtra(&msg)
}
}
这是一个不暂停计时器的跑步:
$ GO TEST -BENCH SPLITMESSAGEEXTRA -BENCHTIME 1SBenchmarkssplitMessageExtra-8 3000000 434 NS/OP经过OK github.com/hubro/logreader 1.730s
这是一个具有上述基准功能的运行:
$ GO TEST -BENCH SPLITMESSAGEEXTRA -BENCHTIME 1SBenchmarkssplitMessageExtra-8 5000000 385 NS/OP经过OK github.com/hubro/logreader 100.563s
请注意,运行需要年龄。
您的代码和基准似乎很慢。这是一个更快的版本。
package main
import (
"strings"
"testing"
)
// Splits the main line from extra information
func splitMessageExtra(line *string) string {
const newline = "#012"
i := strings.Index(*line, newline)
if i < 0 {
return ""
}
extra := (*line)[i+len(newline):]
*line = (*line)[0:i]
return extra
}
var testMessage = `Feb 10 15:16:20 foo_stats[-] (warning): [foo_stats.postfix, line 166, thread "processor_mta03"]: Skipped line because there is no context:#012Feb 10 15:16:20 mta03 postfix/qmgr[7419]: ABCDEF123: from=<>, size=24431, nrcpt=1 (queue active)`
func BenchmarkSplitMessageExtra(b *testing.B) {
for i := 0; i < b.N; i++ {
msg := testMessage
splitMessageExtra(&msg)
}
}
输出:
$ go test -bench=.
goos: linux
goarch: amd64
pkg: extra
BenchmarkSplitMessageExtra-4 50000000 32.2 ns/op
PASS
ok extra 1.647s
进行比较,这是您的代码和基准的结果。您的代码和基准分别比我的:968 NS/OP和50.184s对32.2 NS/OP和1.647S。
package main
import (
"testing"
)
// Splits the main line from extra information
func splitMessageExtra(line *string) string {
var prev rune
for i, char := range *line {
if prev == 0 && char == '#' {
prev = char
continue
}
if prev == '#' && char == '0' {
prev = char
continue
}
if prev == '0' && char == '1' {
prev = char
continue
}
if prev == '1' && char == '2' {
extra := (*line)[i+1:]
*line = (*line)[0 : i-3]
return extra
}
prev = 0
}
return ""
}
var testMessage = `Feb 10 15:16:20 foo_stats[-] (warning): [foo_stats.postfix, line 166, thread "processor_mta03"]: Skipped line because there is no context:#012Feb 10 15:16:20 mta03 postfix/qmgr[7419]: ABCDEF123: from=<>, size=24431, nrcpt=1 (queue active)`
func BenchmarkSplitMessageExtra(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
msg := string([]byte(testMessage))
b.StartTimer()
splitMessageExtra(&msg)
}
}
输出:
$ go test -bench=.
goos: linux
goarch: amd64
pkg: extra
BenchmarkSplitMessageExtra-4 2000000 968 ns/op
PASS
ok extra 50.184s
您的某些代码是不必要的;它使用CPU时间并触发分配。例如,将for i, char := range *line {}
转换为runes,将string
转换为 []byte
转换为 string
, string([]byte(testMessage))
。一些算法可以改进。例如,搜索新线。
没有错。StopTimer
和StartTimer
应该比splitMessageExtra
贵得多。他们俩都致电runtime.ReadMemStats
。请参阅此处。