adventOfCode/runtime/benchmark/benchmark.go
2021-12-08 22:51:18 +00:00

178 lines
No EOL
3.7 KiB
Go

package benchmark
import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/codemicro/adventOfCode/runtime/challenge"
"github.com/codemicro/adventOfCode/runtime/runners"
"github.com/schollz/progressbar/v3"
)
func makeBenchmarkID(part runners.Part, number int) string {
if number == -1 {
return fmt.Sprintf("benchmark.part.%d", part)
}
return fmt.Sprintf("benchmark.part.%d.%d", part, number)
}
func meanFloatSlice(arr []float64) float64 {
var sum float64
for _, v := range arr {
sum += v
}
return sum / float64(len(arr))
}
func minFloatSlice(arr []float64) float64 {
min := arr[0]
for _, v := range arr {
if v < min {
min = v
}
}
return min
}
func maxFloatSlice(arr []float64) float64 {
max := arr[0]
for _, v := range arr {
if v > max {
max = v
}
}
return max
}
func Run(selectedChallenge *challenge.Challenge, input string, numberRuns int) error {
implementations, err := selectedChallenge.GetImplementations()
if err != nil {
return err
}
var valueSets []*values
for _, implementation := range implementations {
v, err := benchmarkImplementation(implementation, selectedChallenge.Dir, input, numberRuns)
if err != nil {
return err
}
valueSets = append(valueSets, v)
}
// make file
jdata := make(map[string]interface{})
jdata["day"] = selectedChallenge.Number
jdata["dir"] = selectedChallenge.Dir
jdata["numRuns"] = numberRuns
jdata["implementations"] = make(map[string]interface{})
for _, vs := range valueSets {
x := make(map[string]interface{})
for _, v := range vs.values {
x[v.key] = v.value
}
(jdata["implementations"].(map[string]interface{}))[vs.implementation] = x
}
fpath := filepath.Join(selectedChallenge.Dir, "benchmark.json")
fmt.Println("Writing results to", fpath)
jBytes, err := json.MarshalIndent(jdata, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(
fpath,
jBytes,
0644,
)
}
type values struct {
implementation string
values []kv
}
type kv struct {
key string
value float64
}
func benchmarkImplementation(implementation string, dir string, inputString string, numberRuns int) (*values, error) {
var results []*runners.Result
runner := runners.Available[implementation](dir)
for i := 0; i < numberRuns; i++ {
runner.Queue(&runners.Task{
TaskID: makeBenchmarkID(runners.PartOne, i),
Part: runners.PartOne,
Input: inputString,
})
runner.Queue(&runners.Task{
TaskID: makeBenchmarkID(runners.PartTwo, i),
Part: runners.PartTwo,
Input: inputString,
})
}
pb := progressbar.NewOptions(
numberRuns * 2, // two parts means 2x the number of runs
progressbar.OptionSetDescription(
fmt.Sprintf("Running %s benchmarks", runners.RunnerNames[implementation]),
),
)
res, cleanup := runner.Run()
for roe := range res {
if roe.Error != nil {
_ = pb.Close()
return nil, roe.Error
}
results = append(results, roe.Result)
_ = pb.Add(1)
}
fmt.Println()
var (
p1, p2 []float64
p1id = makeBenchmarkID(runners.PartOne, -1)
p2id = makeBenchmarkID(runners.PartTwo, -1)
)
for _, result := range results {
if strings.HasPrefix(result.TaskID, p1id) {
p1 = append(p1, result.Duration)
} else if strings.HasPrefix(result.TaskID, p2id) {
p2 = append(p2, result.Duration)
}
}
if cleanup != nil {
cleanup()
}
return &values{
implementation: runners.RunnerNames[implementation],
values: []kv{
{"part.1.avg", meanFloatSlice(p1)},
{"part.1.min", minFloatSlice(p1)},
{"part.1.max", maxFloatSlice(p1)},
{"part.2.avg", meanFloatSlice(p2)},
{"part.2.min", minFloatSlice(p2)},
{"part.2.max", maxFloatSlice(p2)},
},
}, nil
}