xref: /aosp_15_r20/external/starlark-go/starlark/bench_test.go (revision 4947cdc739c985f6d86941e22894f5cefe7c9e9a)
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