summaryrefslogtreecommitdiff
path: root/test/bench/perf/driver.go
blob: 0cdd1455a3072283496aae1982956c5adabe8482 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package main

import (
	"flag"
	"fmt"
	"log"
	"time"
	"runtime"
)

var (
	benchNum  = flag.Int("benchnum", 3, "run each benchmark that many times")
	benchTime = flag.Duration("benchtime", 10*time.Second, "benchmarking time for a single run")
	benchMem  = flag.Int("benchmem", 64, "approx RSS value to aim at in benchmarks, in MB")
)

type PerfResult struct {
	N       int64
	RunTime time.Duration
	Metrics []PerfMetric
}

type PerfMetric struct {
	Type  string
	Val   int64
}

type BenchFunc func(N int64) ([]PerfMetric, error)

func PerfBenchmark(f BenchFunc) {
	if !flag.Parsed() {
		flag.Parse()
	}
	var res PerfResult
	for i := 0; i < *benchNum; i++ {
		res1 := RunBenchmark(f)
		if res.RunTime == 0 || res.RunTime > res1.RunTime {
			res = res1
		}
	}
	fmt.Printf("GOPERF-METRIC:runtime=%v\n", int64(res.RunTime)/res.N)
	for _, m := range res.Metrics {
		fmt.Printf("GOPERF-METRIC:%v=%v\n", m.Type, m.Val)
	}
}

func RunBenchmark(f BenchFunc) PerfResult {
	var res PerfResult
	for ChooseN(&res) {
		log.Printf("Benchmarking %v iterations\n", res.N)
		res = RunOnce(f, res.N)
		log.Printf("Done: %+v\n", res)
	}
	return res
}

func RunOnce(f BenchFunc, N int64) PerfResult {
	runtime.GC()
	mstats0 := new(runtime.MemStats)
	runtime.ReadMemStats(mstats0)
	res := PerfResult{N: N}

	t0 := time.Now()
	var err error
	res.Metrics, err = f(N)
	res.RunTime = time.Since(t0)

	if err != nil {
		log.Fatalf("Benchmark function failed: %v\n", err)
	}

	mstats1 := new(runtime.MemStats)
	runtime.ReadMemStats(mstats1)
	fmt.Printf("%+v\n", *mstats1)
	return res
}

func ChooseN(res *PerfResult) bool {
	const MaxN = 1e12
	last := res.N
	if last == 0 {
		res.N = 1
		return true
	} else if res.RunTime >= *benchTime || last >= MaxN {
		return false
	}
	nsPerOp := max(1, int64(res.RunTime)/last)
	res.N = int64(*benchTime) / nsPerOp
	res.N = max(min(res.N+res.N/2, 100*last), last+1)
	res.N = roundUp(res.N)
	return true
}

func roundUp(n int64) int64 {
	tmp := n
	base := int64(1)
	for tmp >= 10 {
		tmp /= 10
		base *= 10
	}
	switch {
	case n <= base:
		return base
	case n <= (2 * base):
		return 2 * base
	case n <= (5 * base):
		return 5 * base
	default:
		return 10 * base
	}
	panic("unreachable")
	return 0
}

func min(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}

func max(a, b int64) int64 {
	if a > b {
		return a
	}
	return b
}