diff --git a/execext/exec.go b/execext/exec.go index 6d1304cc..b551d2ac 100644 --- a/execext/exec.go +++ b/execext/exec.go @@ -5,11 +5,26 @@ import ( "errors" "io" "strings" + "sync" "mvdan.cc/sh/interp" "mvdan.cc/sh/syntax" ) +var ( + parserPool = sync.Pool{ + New: func() interface{} { + return syntax.NewParser() + }, + } + + runnerPool = sync.Pool{ + New: func() interface{} { + return &interp.Runner{} + }, + } +) + // RunCommandOptions is the options for the RunCommand func type RunCommandOptions struct { Context context.Context @@ -32,19 +47,24 @@ func RunCommand(opts *RunCommandOptions) error { return ErrNilOptions } - p, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "") + parser := parserPool.Get().(*syntax.Parser) + defer parserPool.Put(parser) + + p, err := parser.Parse(strings.NewReader(opts.Command), "") if err != nil { return err } - r := interp.Runner{ - Context: opts.Context, - Dir: opts.Dir, - Env: opts.Env, - Stdin: opts.Stdin, - Stdout: opts.Stdout, - Stderr: opts.Stderr, - } + r := runnerPool.Get().(*interp.Runner) + defer runnerPool.Put(r) + + r.Context = opts.Context + r.Dir = opts.Dir + r.Env = opts.Env + r.Stdin = opts.Stdin + r.Stdout = opts.Stdout + r.Stderr = opts.Stderr + if err = r.Reset(); err != nil { return err } diff --git a/execext/exec_test.go b/execext/exec_test.go new file mode 100644 index 00000000..a02cdcb2 --- /dev/null +++ b/execext/exec_test.go @@ -0,0 +1,69 @@ +// execext_test.go +package execext + +import ( + "context" + "io/ioutil" + "strings" + "sync" + "testing" + + "mvdan.cc/sh/interp" + "mvdan.cc/sh/syntax" +) + +func BenchmarkNoPool(b *testing.B) { + for i := 0; i < b.N; i++ { + f, err := syntax.NewParser().Parse(strings.NewReader(`echo "Hello, World!"`), "") + if err != nil { + panic(err) + } + r := interp.Runner{ + Context: context.TODO(), + Stdout: ioutil.Discard, + Stderr: ioutil.Discard, + } + if err = r.Reset(); err != nil { + panic(err) + } + if err = r.Run(f); err != nil { + panic(err) + } + } +} + +func BenchmarkPool(b *testing.B) { + parserPool := sync.Pool{ + New: func() interface{} { + return syntax.NewParser() + }, + } + runnerPool := sync.Pool{ + New: func() interface{} { + return &interp.Runner{} + }, + } + + for i := 0; i < b.N; i++ { + parser := parserPool.Get().(*syntax.Parser) + defer parserPool.Put(parser) + + f, err := parser.Parse(strings.NewReader(`echo "Hello, World!"`), "") + if err != nil { + panic(err) + } + + r := runnerPool.Get().(*interp.Runner) + defer runnerPool.Put(r) + + r.Stdout = ioutil.Discard + r.Stderr = ioutil.Discard + + if err = r.Reset(); err != nil { + panic(err) + } + if err = r.Run(f); err != nil { + panic(err) + } + } +}