Skip to content

Commit 6a44a3a

Browse files
committed
test/bench/go1: eliminate start-up time
The go1 benchmark suite does a lot of work at package init time, which makes it take quite a while to run even if you're not running any of the benchmarks, or if you're only running a subset of them. This leads to an awkward workaround in dist test to compile but not run the package, unlike roughly all other packages. It also reduces isolation between benchmarks by affecting the starting heap size of all benchmarks. Fix this by initializing all data required by a benchmark when that benchmark runs, and keeping it local so it gets freed by the GC and doesn't leak between benchmarks. Now, none of the benchmarks depend on global state. Re-initializing the data on each benchmark run does add overhead to an actual benchmark run, as each benchmark function is called several times with different values of b.N. A full run of all benchmarks at the default -benchtime=1s now takes ~10% longer; higher -benchtimes would be less. It would be quite difficult to cache this data between invocations of the same benchmark function without leaking between different benchmarks and affecting GC overheads, as the testing package doesn't provide any mechanism for this. This reduces the time to run the binary with no benchmarks from 1.5 seconds to 10 ms, and also reduces the memory required to do this from 342 MiB to 17 MiB. To make sure data was not leaking between different benchmarks, I ran the benchmarks with -shuffle=on. The variance remained low: mostly under 3%. A few benchmarks had higher variance, but in all cases it was similar to the variance between this change. This CL naturally changes the measured performance of several of the benchmarks because it dramatically changes the heap size and hence GC overheads. However, going forward the benchmarks should be much better isolated. For #37486. Change-Id: I252ebea703a9560706cc1990dc5ad22d1927c7a0 Reviewed-on: https://go-review.googlesource.com/c/go/+/443336 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Run-TryBot: Austin Clements <[email protected]>
1 parent 767df51 commit 6a44a3a

File tree

7 files changed

+69
-57
lines changed

7 files changed

+69
-57
lines changed

src/cmd/dist/test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -828,9 +828,8 @@ func (t *tester) registerTests() {
828828

829829
if goos != "android" && !t.iOS() {
830830
// There are no tests in this directory, only benchmarks.
831-
// Check that the test binary builds but don't bother running it.
832-
// (It has init-time work to set up for the benchmarks that is not worth doing unnecessarily.)
833-
t.registerTest("bench_go1", "../test/bench/go1", t.goTest(), "-c", "-o="+os.DevNull)
831+
// Check that the test binary builds.
832+
t.registerTest("bench_go1", "../test/bench/go1", t.goTest(), ".")
834833
}
835834
if goos != "android" && !t.iOS() {
836835
// Only start multiple test dir shards on builders,

test/bench/go1/fasta_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import "runtime"
88

99
// Not a benchmark; input for revcomp.
1010

11-
var fastabytes = makefasta()
12-
1311
func makefasta() []byte {
1412
var n int = 25e6
1513
if runtime.GOARCH == "arm" || runtime.GOARCH == "mips" || runtime.GOARCH == "mips64" {

test/bench/go1/gob_test.go

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,28 @@ import (
1616
"testing"
1717
)
1818

19-
var (
20-
gobbytes []byte
21-
gobdata *JSONResponse
22-
)
23-
24-
func init() {
25-
gobdata = gobResponse(&jsondata)
19+
func makeGob(jsondata *JSONResponse) (data *JSONResponse, b []byte) {
20+
data = gobResponse(jsondata)
2621

2722
var buf bytes.Buffer
28-
if err := gob.NewEncoder(&buf).Encode(gobdata); err != nil {
23+
if err := gob.NewEncoder(&buf).Encode(data); err != nil {
2924
panic(err)
3025
}
31-
gobbytes = buf.Bytes()
26+
b = buf.Bytes()
3227

3328
var r JSONResponse
34-
if err := gob.NewDecoder(bytes.NewBuffer(gobbytes)).Decode(&r); err != nil {
29+
if err := gob.NewDecoder(bytes.NewBuffer(b)).Decode(&r); err != nil {
3530
panic(err)
3631
}
37-
if !reflect.DeepEqual(gobdata, &r) {
32+
if !reflect.DeepEqual(data, &r) {
3833
log.Printf("%v\n%v", jsondata, r)
3934
b, _ := json.Marshal(&jsondata)
4035
br, _ := json.Marshal(&r)
4136
log.Printf("%s\n%s\n", b, br)
4237
panic("gob: encode+decode lost data")
4338
}
39+
40+
return
4441
}
4542

4643
// gob turns [] into null, so make a copy of the data structure like that
@@ -61,33 +58,38 @@ func gobNode(n *JSONNode) *JSONNode {
6158
return n1
6259
}
6360

64-
func gobdec() {
65-
if gobbytes == nil {
66-
panic("gobdata not initialized")
67-
}
61+
func gobdec(b []byte) {
6862
var r JSONResponse
69-
if err := gob.NewDecoder(bytes.NewBuffer(gobbytes)).Decode(&r); err != nil {
63+
if err := gob.NewDecoder(bytes.NewBuffer(b)).Decode(&r); err != nil {
7064
panic(err)
7165
}
7266
_ = r
7367
}
7468

75-
func gobenc() {
76-
if err := gob.NewEncoder(io.Discard).Encode(&gobdata); err != nil {
69+
func gobenc(data *JSONResponse) {
70+
if err := gob.NewEncoder(io.Discard).Encode(data); err != nil {
7771
panic(err)
7872
}
7973
}
8074

8175
func BenchmarkGobDecode(b *testing.B) {
82-
b.SetBytes(int64(len(gobbytes)))
76+
jsonbytes := makeJsonBytes()
77+
jsondata := makeJsonData(jsonbytes)
78+
_, bytes := makeGob(jsondata)
79+
b.ResetTimer()
80+
b.SetBytes(int64(len(bytes)))
8381
for i := 0; i < b.N; i++ {
84-
gobdec()
82+
gobdec(bytes)
8583
}
8684
}
8785

8886
func BenchmarkGobEncode(b *testing.B) {
89-
b.SetBytes(int64(len(gobbytes)))
87+
jsonbytes := makeJsonBytes()
88+
jsondata := makeJsonData(jsonbytes)
89+
data, bytes := makeGob(jsondata)
90+
b.ResetTimer()
91+
b.SetBytes(int64(len(bytes)))
9092
for i := 0; i < b.N; i++ {
91-
gobenc()
93+
gobenc(data)
9294
}
9395
}

test/bench/go1/gzip_test.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,19 @@ import (
1313
"testing"
1414
)
1515

16-
var (
17-
jsongunz = bytes.Repeat(jsonbytes, 10)
18-
jsongz []byte
19-
)
16+
func makeGunzip(jsonbytes []byte) []byte {
17+
return bytes.Repeat(jsonbytes, 10)
18+
}
2019

21-
func init() {
20+
func makeGzip(jsongunz []byte) []byte {
2221
var buf bytes.Buffer
2322
c := gz.NewWriter(&buf)
2423
c.Write(jsongunz)
2524
c.Close()
26-
jsongz = buf.Bytes()
25+
return buf.Bytes()
2726
}
2827

29-
func gzip() {
28+
func gzip(jsongunz []byte) {
3029
c := gz.NewWriter(io.Discard)
3130
if _, err := c.Write(jsongunz); err != nil {
3231
panic(err)
@@ -36,7 +35,7 @@ func gzip() {
3635
}
3736
}
3837

39-
func gunzip() {
38+
func gunzip(jsongz []byte) {
4039
r, err := gz.NewReader(bytes.NewBuffer(jsongz))
4140
if err != nil {
4241
panic(err)
@@ -48,15 +47,22 @@ func gunzip() {
4847
}
4948

5049
func BenchmarkGzip(b *testing.B) {
50+
jsonbytes := makeJsonBytes()
51+
jsongunz := makeGunzip(jsonbytes)
52+
b.ResetTimer()
5153
b.SetBytes(int64(len(jsongunz)))
5254
for i := 0; i < b.N; i++ {
53-
gzip()
55+
gzip(jsongunz)
5456
}
5557
}
5658

5759
func BenchmarkGunzip(b *testing.B) {
60+
jsonbytes := makeJsonBytes()
61+
jsongunz := makeGunzip(jsonbytes)
62+
jsongz := makeGzip(jsongunz)
63+
b.ResetTimer()
5864
b.SetBytes(int64(len(jsongunz)))
5965
for i := 0; i < b.N; i++ {
60-
gunzip()
66+
gunzip(jsongz)
6167
}
6268
}

test/bench/go1/json_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ import (
1515
"testing"
1616
)
1717

18-
var (
19-
jsonbytes = makeJsonBytes()
20-
jsondata = makeJsonData()
21-
)
22-
2318
func makeJsonBytes() []byte {
2419
var r io.Reader
2520
r = bytes.NewReader(bytes.Replace(jsonbz2_base64, []byte{'\n'}, nil, -1))
@@ -32,12 +27,12 @@ func makeJsonBytes() []byte {
3227
return b
3328
}
3429

35-
func makeJsonData() JSONResponse {
30+
func makeJsonData(jsonbytes []byte) *JSONResponse {
3631
var v JSONResponse
3732
if err := json.Unmarshal(jsonbytes, &v); err != nil {
3833
panic(err)
3934
}
40-
return v
35+
return &v
4136
}
4237

4338
type JSONResponse struct {
@@ -55,32 +50,37 @@ type JSONNode struct {
5550
MeanT int64 `json:"mean_t"`
5651
}
5752

58-
func jsondec() {
53+
func jsondec(bytes []byte) {
5954
var r JSONResponse
60-
if err := json.Unmarshal(jsonbytes, &r); err != nil {
55+
if err := json.Unmarshal(bytes, &r); err != nil {
6156
panic(err)
6257
}
6358
_ = r
6459
}
6560

66-
func jsonenc() {
67-
buf, err := json.Marshal(&jsondata)
61+
func jsonenc(data *JSONResponse) {
62+
buf, err := json.Marshal(data)
6863
if err != nil {
6964
panic(err)
7065
}
7166
_ = buf
7267
}
7368

7469
func BenchmarkJSONEncode(b *testing.B) {
70+
jsonbytes := makeJsonBytes()
71+
jsondata := makeJsonData(jsonbytes)
72+
b.ResetTimer()
7573
b.SetBytes(int64(len(jsonbytes)))
7674
for i := 0; i < b.N; i++ {
77-
jsonenc()
75+
jsonenc(jsondata)
7876
}
7977
}
8078

8179
func BenchmarkJSONDecode(b *testing.B) {
80+
jsonbytes := makeJsonBytes()
81+
b.ResetTimer()
8282
b.SetBytes(int64(len(jsonbytes)))
8383
for i := 0; i < b.N; i++ {
84-
jsondec()
84+
jsondec(jsonbytes)
8585
}
8686
}

test/bench/go1/revcomp_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ func revcomp(data []byte) {
7878
}
7979

8080
func BenchmarkRevcomp(b *testing.B) {
81-
b.SetBytes(int64(len(fastabytes)))
81+
bytes := makefasta()
82+
b.ResetTimer()
83+
b.SetBytes(int64(len(bytes)))
8284
for i := 0; i < b.N; i++ {
83-
revcomp(fastabytes)
85+
revcomp(bytes)
8486
}
8587
}

test/bench/go1/template_test.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ func stripTabNL(r rune) rune {
4949
return r
5050
}
5151

52-
var tmpl = template.Must(template.New("main").Parse(strings.Map(stripTabNL, tmplText)))
52+
func makeTemplate(jsonbytes []byte, jsondata *JSONResponse) *template.Template {
53+
tmpl := template.Must(template.New("main").Parse(strings.Map(stripTabNL, tmplText)))
5354

54-
func init() {
5555
var buf bytes.Buffer
5656
if err := tmpl.Execute(&buf, &jsondata); err != nil {
5757
panic(err)
@@ -60,17 +60,22 @@ func init() {
6060
println(buf.Len(), len(jsonbytes))
6161
panic("wrong output")
6262
}
63+
return tmpl
6364
}
6465

65-
func tmplexec() {
66-
if err := tmpl.Execute(io.Discard, &jsondata); err != nil {
66+
func tmplexec(tmpl *template.Template, jsondata *JSONResponse) {
67+
if err := tmpl.Execute(io.Discard, jsondata); err != nil {
6768
panic(err)
6869
}
6970
}
7071

7172
func BenchmarkTemplate(b *testing.B) {
73+
jsonbytes := makeJsonBytes()
74+
jsondata := makeJsonData(jsonbytes)
75+
tmpl := makeTemplate(jsonbytes, jsondata)
76+
b.ResetTimer()
7277
b.SetBytes(int64(len(jsonbytes)))
7378
for i := 0; i < b.N; i++ {
74-
tmplexec()
79+
tmplexec(tmpl, jsondata)
7580
}
7681
}

0 commit comments

Comments
 (0)