1*4947cdc7SCole Faust// Copyright 2018 The Bazel Authors. All rights reserved. 2*4947cdc7SCole Faust// Use of this source code is governed by a BSD-style 3*4947cdc7SCole Faust// license that can be found in the LICENSE file. 4*4947cdc7SCole Faust 5*4947cdc7SCole Faustpackage starlark_test 6*4947cdc7SCole Faust 7*4947cdc7SCole Faustimport ( 8*4947cdc7SCole Faust "bytes" 9*4947cdc7SCole Faust "fmt" 10*4947cdc7SCole Faust "io/ioutil" 11*4947cdc7SCole Faust "path/filepath" 12*4947cdc7SCole Faust "strings" 13*4947cdc7SCole Faust "testing" 14*4947cdc7SCole Faust 15*4947cdc7SCole Faust "go.starlark.net/starlark" 16*4947cdc7SCole Faust "go.starlark.net/starlarktest" 17*4947cdc7SCole Faust) 18*4947cdc7SCole Faust 19*4947cdc7SCole Faustfunc Benchmark(b *testing.B) { 20*4947cdc7SCole Faust defer setOptions("") 21*4947cdc7SCole Faust 22*4947cdc7SCole Faust testdata := starlarktest.DataFile("starlark", ".") 23*4947cdc7SCole Faust thread := new(starlark.Thread) 24*4947cdc7SCole Faust for _, file := range []string{ 25*4947cdc7SCole Faust "testdata/benchmark.star", 26*4947cdc7SCole Faust // ... 27*4947cdc7SCole Faust } { 28*4947cdc7SCole Faust 29*4947cdc7SCole Faust filename := filepath.Join(testdata, file) 30*4947cdc7SCole Faust 31*4947cdc7SCole Faust src, err := ioutil.ReadFile(filename) 32*4947cdc7SCole Faust if err != nil { 33*4947cdc7SCole Faust b.Error(err) 34*4947cdc7SCole Faust continue 35*4947cdc7SCole Faust } 36*4947cdc7SCole Faust setOptions(string(src)) 37*4947cdc7SCole Faust 38*4947cdc7SCole Faust // Evaluate the file once. 39*4947cdc7SCole Faust globals, err := starlark.ExecFile(thread, filename, src, nil) 40*4947cdc7SCole Faust if err != nil { 41*4947cdc7SCole Faust reportEvalError(b, err) 42*4947cdc7SCole Faust } 43*4947cdc7SCole Faust 44*4947cdc7SCole Faust // Repeatedly call each global function named bench_* as a benchmark. 45*4947cdc7SCole Faust for _, name := range globals.Keys() { 46*4947cdc7SCole Faust value := globals[name] 47*4947cdc7SCole Faust if fn, ok := value.(*starlark.Function); ok && strings.HasPrefix(name, "bench_") { 48*4947cdc7SCole Faust b.Run(name, func(b *testing.B) { 49*4947cdc7SCole Faust _, err := starlark.Call(thread, fn, starlark.Tuple{benchmark{b}}, nil) 50*4947cdc7SCole Faust if err != nil { 51*4947cdc7SCole Faust reportEvalError(b, err) 52*4947cdc7SCole Faust } 53*4947cdc7SCole Faust }) 54*4947cdc7SCole Faust } 55*4947cdc7SCole Faust } 56*4947cdc7SCole Faust } 57*4947cdc7SCole Faust} 58*4947cdc7SCole Faust 59*4947cdc7SCole Faust// A benchmark is passed to each bench_xyz(b) function in a bench_*.star file. 60*4947cdc7SCole Faust// It provides b.n, the number of iterations that must be executed by the function, 61*4947cdc7SCole Faust// which is typically of the form: 62*4947cdc7SCole Faust// 63*4947cdc7SCole Faust// def bench_foo(b): 64*4947cdc7SCole Faust// for _ in range(b.n): 65*4947cdc7SCole Faust// ...work... 66*4947cdc7SCole Faust// 67*4947cdc7SCole Faust// It also provides stop, start, and restart methods to stop the clock in case 68*4947cdc7SCole Faust// there is significant set-up work that should not count against the measured 69*4947cdc7SCole Faust// operation. 70*4947cdc7SCole Faust// 71*4947cdc7SCole Faust// (This interface is inspired by Go's testing.B, and is also implemented 72*4947cdc7SCole Faust// by the java.starlark.net implementation; see 73*4947cdc7SCole Faust// https://github.com/bazelbuild/starlark/pull/75#pullrequestreview-275604129.) 74*4947cdc7SCole Fausttype benchmark struct { 75*4947cdc7SCole Faust b *testing.B 76*4947cdc7SCole Faust} 77*4947cdc7SCole Faust 78*4947cdc7SCole Faustfunc (benchmark) Freeze() {} 79*4947cdc7SCole Faustfunc (benchmark) Truth() starlark.Bool { return true } 80*4947cdc7SCole Faustfunc (benchmark) Type() string { return "benchmark" } 81*4947cdc7SCole Faustfunc (benchmark) String() string { return "<benchmark>" } 82*4947cdc7SCole Faustfunc (benchmark) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: benchmark") } 83*4947cdc7SCole Faustfunc (benchmark) AttrNames() []string { return []string{"n", "restart", "start", "stop"} } 84*4947cdc7SCole Faustfunc (b benchmark) Attr(name string) (starlark.Value, error) { 85*4947cdc7SCole Faust switch name { 86*4947cdc7SCole Faust case "n": 87*4947cdc7SCole Faust return starlark.MakeInt(b.b.N), nil 88*4947cdc7SCole Faust case "restart": 89*4947cdc7SCole Faust return benchmarkRestart.BindReceiver(b), nil 90*4947cdc7SCole Faust case "start": 91*4947cdc7SCole Faust return benchmarkStart.BindReceiver(b), nil 92*4947cdc7SCole Faust case "stop": 93*4947cdc7SCole Faust return benchmarkStop.BindReceiver(b), nil 94*4947cdc7SCole Faust } 95*4947cdc7SCole Faust return nil, nil 96*4947cdc7SCole Faust} 97*4947cdc7SCole Faust 98*4947cdc7SCole Faustvar ( 99*4947cdc7SCole Faust benchmarkRestart = starlark.NewBuiltin("restart", benchmarkRestartImpl) 100*4947cdc7SCole Faust benchmarkStart = starlark.NewBuiltin("start", benchmarkStartImpl) 101*4947cdc7SCole Faust benchmarkStop = starlark.NewBuiltin("stop", benchmarkStopImpl) 102*4947cdc7SCole Faust) 103*4947cdc7SCole Faust 104*4947cdc7SCole Faustfunc benchmarkRestartImpl(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 105*4947cdc7SCole Faust b.Receiver().(benchmark).b.ResetTimer() 106*4947cdc7SCole Faust return starlark.None, nil 107*4947cdc7SCole Faust} 108*4947cdc7SCole Faust 109*4947cdc7SCole Faustfunc benchmarkStartImpl(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 110*4947cdc7SCole Faust b.Receiver().(benchmark).b.StartTimer() 111*4947cdc7SCole Faust return starlark.None, nil 112*4947cdc7SCole Faust} 113*4947cdc7SCole Faust 114*4947cdc7SCole Faustfunc benchmarkStopImpl(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 115*4947cdc7SCole Faust b.Receiver().(benchmark).b.StopTimer() 116*4947cdc7SCole Faust return starlark.None, nil 117*4947cdc7SCole Faust} 118*4947cdc7SCole Faust 119*4947cdc7SCole Faust// BenchmarkProgram measures operations relevant to compiled programs. 120*4947cdc7SCole Faust// TODO(adonovan): use a bigger testdata program. 121*4947cdc7SCole Faustfunc BenchmarkProgram(b *testing.B) { 122*4947cdc7SCole Faust // Measure time to read a source file (approx 600us but depends on hardware and file system). 123*4947cdc7SCole Faust filename := starlarktest.DataFile("starlark", "testdata/paths.star") 124*4947cdc7SCole Faust var src []byte 125*4947cdc7SCole Faust b.Run("read", func(b *testing.B) { 126*4947cdc7SCole Faust for i := 0; i < b.N; i++ { 127*4947cdc7SCole Faust var err error 128*4947cdc7SCole Faust src, err = ioutil.ReadFile(filename) 129*4947cdc7SCole Faust if err != nil { 130*4947cdc7SCole Faust b.Fatal(err) 131*4947cdc7SCole Faust } 132*4947cdc7SCole Faust } 133*4947cdc7SCole Faust }) 134*4947cdc7SCole Faust 135*4947cdc7SCole Faust // Measure time to turn a source filename into a compiled program (approx 450us). 136*4947cdc7SCole Faust var prog *starlark.Program 137*4947cdc7SCole Faust b.Run("compile", func(b *testing.B) { 138*4947cdc7SCole Faust for i := 0; i < b.N; i++ { 139*4947cdc7SCole Faust var err error 140*4947cdc7SCole Faust _, prog, err = starlark.SourceProgram(filename, src, starlark.StringDict(nil).Has) 141*4947cdc7SCole Faust if err != nil { 142*4947cdc7SCole Faust b.Fatal(err) 143*4947cdc7SCole Faust } 144*4947cdc7SCole Faust } 145*4947cdc7SCole Faust }) 146*4947cdc7SCole Faust 147*4947cdc7SCole Faust // Measure time to encode a compiled program to a memory buffer 148*4947cdc7SCole Faust // (approx 20us; was 75-120us with gob encoding). 149*4947cdc7SCole Faust var out bytes.Buffer 150*4947cdc7SCole Faust b.Run("encode", func(b *testing.B) { 151*4947cdc7SCole Faust for i := 0; i < b.N; i++ { 152*4947cdc7SCole Faust out.Reset() 153*4947cdc7SCole Faust if err := prog.Write(&out); err != nil { 154*4947cdc7SCole Faust b.Fatal(err) 155*4947cdc7SCole Faust } 156*4947cdc7SCole Faust } 157*4947cdc7SCole Faust }) 158*4947cdc7SCole Faust 159*4947cdc7SCole Faust // Measure time to decode a compiled program from a memory buffer 160*4947cdc7SCole Faust // (approx 20us; was 135-250us with gob encoding) 161*4947cdc7SCole Faust b.Run("decode", func(b *testing.B) { 162*4947cdc7SCole Faust for i := 0; i < b.N; i++ { 163*4947cdc7SCole Faust in := bytes.NewReader(out.Bytes()) 164*4947cdc7SCole Faust if _, err := starlark.CompiledProgram(in); err != nil { 165*4947cdc7SCole Faust b.Fatal(err) 166*4947cdc7SCole Faust } 167*4947cdc7SCole Faust } 168*4947cdc7SCole Faust }) 169*4947cdc7SCole Faust} 170