mirror of
https://github.com/golang/go.git
synced 2025-05-20 23:03:26 +00:00
text/template/parse: speed up nodes printing
This CL is a follow up for 198080. Added a private writeTo method to the Node interface, in order to use the same builder for printing all nodes in the tree. Benchmark output against master: benchmark old ns/op new ns/op delta BenchmarkParseLarge-8 24594994 25292054 +2.83% BenchmarkVariableString-8 117 118 +0.85% BenchmarkListString-8 10475 3353 -67.99% benchmark old allocs new allocs delta BenchmarkVariableString-8 3 3 +0.00% BenchmarkListString-8 149 31 -79.19% benchmark old bytes new bytes delta BenchmarkVariableString-8 72 72 +0.00% BenchmarkListString-8 5698 1608 -71.78% Change-Id: I2b1cf07cda65c1b80083fb99671289423700feba Reviewed-on: https://go-review.googlesource.com/c/go/+/198278 Reviewed-by: Rob Pike <r@golang.org> Run-TryBot: Rob Pike <r@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
f0e940ebc9
commit
debbb1e78d
@ -28,6 +28,8 @@ type Node interface {
|
|||||||
// tree returns the containing *Tree.
|
// tree returns the containing *Tree.
|
||||||
// It is unexported so all implementations of Node are in this package.
|
// It is unexported so all implementations of Node are in this package.
|
||||||
tree() *Tree
|
tree() *Tree
|
||||||
|
// writeTo writes the String output to the builder.
|
||||||
|
writeTo(*strings.Builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeType identifies the type of a parse tree node.
|
// NodeType identifies the type of a parse tree node.
|
||||||
@ -94,12 +96,16 @@ func (l *ListNode) tree() *Tree {
|
|||||||
|
|
||||||
func (l *ListNode) String() string {
|
func (l *ListNode) String() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for _, n := range l.Nodes {
|
l.writeTo(&sb)
|
||||||
sb.WriteString(n.String())
|
|
||||||
}
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) writeTo(sb *strings.Builder) {
|
||||||
|
for _, n := range l.Nodes {
|
||||||
|
n.writeTo(sb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *ListNode) CopyList() *ListNode {
|
func (l *ListNode) CopyList() *ListNode {
|
||||||
if l == nil {
|
if l == nil {
|
||||||
return l
|
return l
|
||||||
@ -131,6 +137,10 @@ func (t *TextNode) String() string {
|
|||||||
return fmt.Sprintf(textFormat, t.Text)
|
return fmt.Sprintf(textFormat, t.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString(t.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TextNode) tree() *Tree {
|
func (t *TextNode) tree() *Tree {
|
||||||
return t.tr
|
return t.tr
|
||||||
}
|
}
|
||||||
@ -160,12 +170,17 @@ func (p *PipeNode) append(command *CommandNode) {
|
|||||||
|
|
||||||
func (p *PipeNode) String() string {
|
func (p *PipeNode) String() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
p.writeTo(&sb)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) writeTo(sb *strings.Builder) {
|
||||||
if len(p.Decl) > 0 {
|
if len(p.Decl) > 0 {
|
||||||
for i, v := range p.Decl {
|
for i, v := range p.Decl {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
sb.WriteString(", ")
|
sb.WriteString(", ")
|
||||||
}
|
}
|
||||||
sb.WriteString(v.String())
|
v.writeTo(sb)
|
||||||
}
|
}
|
||||||
sb.WriteString(" := ")
|
sb.WriteString(" := ")
|
||||||
}
|
}
|
||||||
@ -173,9 +188,8 @@ func (p *PipeNode) String() string {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
sb.WriteString(" | ")
|
sb.WriteString(" | ")
|
||||||
}
|
}
|
||||||
sb.WriteString(c.String())
|
c.writeTo(sb)
|
||||||
}
|
}
|
||||||
return sb.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PipeNode) tree() *Tree {
|
func (p *PipeNode) tree() *Tree {
|
||||||
@ -218,8 +232,15 @@ func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ActionNode) String() string {
|
func (a *ActionNode) String() string {
|
||||||
return fmt.Sprintf("{{%s}}", a.Pipe)
|
var sb strings.Builder
|
||||||
|
a.writeTo(&sb)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString("{{")
|
||||||
|
a.Pipe.writeTo(sb)
|
||||||
|
sb.WriteString("}}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ActionNode) tree() *Tree {
|
func (a *ActionNode) tree() *Tree {
|
||||||
@ -249,19 +270,23 @@ func (c *CommandNode) append(arg Node) {
|
|||||||
|
|
||||||
func (c *CommandNode) String() string {
|
func (c *CommandNode) String() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
c.writeTo(&sb)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) writeTo(sb *strings.Builder) {
|
||||||
for i, arg := range c.Args {
|
for i, arg := range c.Args {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
sb.WriteByte(' ')
|
sb.WriteByte(' ')
|
||||||
}
|
}
|
||||||
if arg, ok := arg.(*PipeNode); ok {
|
if arg, ok := arg.(*PipeNode); ok {
|
||||||
sb.WriteByte('(')
|
sb.WriteByte('(')
|
||||||
sb.WriteString(arg.String())
|
arg.writeTo(sb)
|
||||||
sb.WriteByte(')')
|
sb.WriteByte(')')
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sb.WriteString(arg.String())
|
arg.writeTo(sb)
|
||||||
}
|
}
|
||||||
return sb.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandNode) tree() *Tree {
|
func (c *CommandNode) tree() *Tree {
|
||||||
@ -312,6 +337,10 @@ func (i *IdentifierNode) String() string {
|
|||||||
return i.Ident
|
return i.Ident
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString(i.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (i *IdentifierNode) tree() *Tree {
|
func (i *IdentifierNode) tree() *Tree {
|
||||||
return i.tr
|
return i.tr
|
||||||
}
|
}
|
||||||
@ -335,13 +364,17 @@ func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
|||||||
|
|
||||||
func (v *VariableNode) String() string {
|
func (v *VariableNode) String() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
v.writeTo(&sb)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) writeTo(sb *strings.Builder) {
|
||||||
for i, id := range v.Ident {
|
for i, id := range v.Ident {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
sb.WriteByte('.')
|
sb.WriteByte('.')
|
||||||
}
|
}
|
||||||
sb.WriteString(id)
|
sb.WriteString(id)
|
||||||
}
|
}
|
||||||
return sb.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VariableNode) tree() *Tree {
|
func (v *VariableNode) tree() *Tree {
|
||||||
@ -374,6 +407,10 @@ func (d *DotNode) String() string {
|
|||||||
return "."
|
return "."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString(d.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DotNode) tree() *Tree {
|
func (d *DotNode) tree() *Tree {
|
||||||
return d.tr
|
return d.tr
|
||||||
}
|
}
|
||||||
@ -404,6 +441,10 @@ func (n *NilNode) String() string {
|
|||||||
return "nil"
|
return "nil"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString(n.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (n *NilNode) tree() *Tree {
|
func (n *NilNode) tree() *Tree {
|
||||||
return n.tr
|
return n.tr
|
||||||
}
|
}
|
||||||
@ -428,11 +469,15 @@ func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
|||||||
|
|
||||||
func (f *FieldNode) String() string {
|
func (f *FieldNode) String() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
f.writeTo(&sb)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) writeTo(sb *strings.Builder) {
|
||||||
for _, id := range f.Ident {
|
for _, id := range f.Ident {
|
||||||
sb.WriteByte('.')
|
sb.WriteByte('.')
|
||||||
sb.WriteString(id)
|
sb.WriteString(id)
|
||||||
}
|
}
|
||||||
return sb.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FieldNode) tree() *Tree {
|
func (f *FieldNode) tree() *Tree {
|
||||||
@ -472,18 +517,22 @@ func (c *ChainNode) Add(field string) {
|
|||||||
|
|
||||||
func (c *ChainNode) String() string {
|
func (c *ChainNode) String() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
c.writeTo(&sb)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) writeTo(sb *strings.Builder) {
|
||||||
if _, ok := c.Node.(*PipeNode); ok {
|
if _, ok := c.Node.(*PipeNode); ok {
|
||||||
sb.WriteByte('(')
|
sb.WriteByte('(')
|
||||||
sb.WriteString(c.Node.String())
|
c.Node.writeTo(sb)
|
||||||
sb.WriteByte(')')
|
sb.WriteByte(')')
|
||||||
} else {
|
} else {
|
||||||
sb.WriteString(c.Node.String())
|
c.Node.writeTo(sb)
|
||||||
}
|
}
|
||||||
for _, field := range c.Field {
|
for _, field := range c.Field {
|
||||||
sb.WriteByte('.')
|
sb.WriteByte('.')
|
||||||
sb.WriteString(field)
|
sb.WriteString(field)
|
||||||
}
|
}
|
||||||
return sb.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChainNode) tree() *Tree {
|
func (c *ChainNode) tree() *Tree {
|
||||||
@ -513,6 +562,10 @@ func (b *BoolNode) String() string {
|
|||||||
return "false"
|
return "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString(b.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BoolNode) tree() *Tree {
|
func (b *BoolNode) tree() *Tree {
|
||||||
return b.tr
|
return b.tr
|
||||||
}
|
}
|
||||||
@ -646,6 +699,10 @@ func (n *NumberNode) String() string {
|
|||||||
return n.Text
|
return n.Text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString(n.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (n *NumberNode) tree() *Tree {
|
func (n *NumberNode) tree() *Tree {
|
||||||
return n.tr
|
return n.tr
|
||||||
}
|
}
|
||||||
@ -673,6 +730,10 @@ func (s *StringNode) String() string {
|
|||||||
return s.Quoted
|
return s.Quoted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString(s.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StringNode) tree() *Tree {
|
func (s *StringNode) tree() *Tree {
|
||||||
return s.tr
|
return s.tr
|
||||||
}
|
}
|
||||||
@ -697,6 +758,10 @@ func (e *endNode) String() string {
|
|||||||
return "{{end}}"
|
return "{{end}}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *endNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString(e.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (e *endNode) tree() *Tree {
|
func (e *endNode) tree() *Tree {
|
||||||
return e.tr
|
return e.tr
|
||||||
}
|
}
|
||||||
@ -725,6 +790,10 @@ func (e *elseNode) String() string {
|
|||||||
return "{{else}}"
|
return "{{else}}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString(e.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (e *elseNode) tree() *Tree {
|
func (e *elseNode) tree() *Tree {
|
||||||
return e.tr
|
return e.tr
|
||||||
}
|
}
|
||||||
@ -745,6 +814,12 @@ type BranchNode struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *BranchNode) String() string {
|
func (b *BranchNode) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
b.writeTo(&sb)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) writeTo(sb *strings.Builder) {
|
||||||
name := ""
|
name := ""
|
||||||
switch b.NodeType {
|
switch b.NodeType {
|
||||||
case NodeIf:
|
case NodeIf:
|
||||||
@ -756,10 +831,17 @@ func (b *BranchNode) String() string {
|
|||||||
default:
|
default:
|
||||||
panic("unknown branch type")
|
panic("unknown branch type")
|
||||||
}
|
}
|
||||||
|
sb.WriteString("{{")
|
||||||
|
sb.WriteString(name)
|
||||||
|
sb.WriteByte(' ')
|
||||||
|
b.Pipe.writeTo(sb)
|
||||||
|
sb.WriteString("}}")
|
||||||
|
b.List.writeTo(sb)
|
||||||
if b.ElseList != nil {
|
if b.ElseList != nil {
|
||||||
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
sb.WriteString("{{else}}")
|
||||||
|
b.ElseList.writeTo(sb)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
sb.WriteString("{{end}}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BranchNode) tree() *Tree {
|
func (b *BranchNode) tree() *Tree {
|
||||||
@ -833,10 +915,19 @@ func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *Temp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TemplateNode) String() string {
|
func (t *TemplateNode) String() string {
|
||||||
if t.Pipe == nil {
|
var sb strings.Builder
|
||||||
return fmt.Sprintf("{{template %q}}", t.Name)
|
t.writeTo(&sb)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) writeTo(sb *strings.Builder) {
|
||||||
|
sb.WriteString("{{template ")
|
||||||
|
sb.WriteString(strconv.Quote(t.Name))
|
||||||
|
if t.Pipe != nil {
|
||||||
|
sb.WriteByte(' ')
|
||||||
|
t.Pipe.writeTo(sb)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
sb.WriteString("}}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TemplateNode) tree() *Tree {
|
func (t *TemplateNode) tree() *Tree {
|
||||||
|
@ -304,7 +304,8 @@ var parseTests = []parseTest{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var builtins = map[string]interface{}{
|
var builtins = map[string]interface{}{
|
||||||
"printf": fmt.Sprintf,
|
"printf": fmt.Sprintf,
|
||||||
|
"contains": strings.Contains,
|
||||||
}
|
}
|
||||||
|
|
||||||
func testParse(doCopy bool, t *testing.T) {
|
func testParse(doCopy bool, t *testing.T) {
|
||||||
@ -571,7 +572,24 @@ func BenchmarkVariableString(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkListString(b *testing.B) {
|
func BenchmarkListString(b *testing.B) {
|
||||||
text := `{{ (printf .Field1.Field2.Field3).Value }}`
|
text := `
|
||||||
|
{{(printf .Field1.Field2.Field3).Value}}
|
||||||
|
{{$x := (printf .Field1.Field2.Field3).Value}}
|
||||||
|
{{$y := (printf $x.Field1.Field2.Field3).Value}}
|
||||||
|
{{$z := $y.Field1.Field2.Field3}}
|
||||||
|
{{if contains $y $z}}
|
||||||
|
{{printf "%q" $y}}
|
||||||
|
{{else}}
|
||||||
|
{{printf "%q" $x}}
|
||||||
|
{{end}}
|
||||||
|
{{with $z.Field1 | contains "boring"}}
|
||||||
|
{{printf "%q" . | printf "%s"}}
|
||||||
|
{{else}}
|
||||||
|
{{printf "%d %d %d" 11 11 11}}
|
||||||
|
{{printf "%d %d %s" 22 22 $x.Field1.Field2.Field3 | printf "%s"}}
|
||||||
|
{{printf "%v" (contains $z.Field1.Field2 $y)}}
|
||||||
|
{{end}}
|
||||||
|
`
|
||||||
tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
|
tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user