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