]> Cypherpunks repositories - gostls13.git/commitdiff
text/template/parse: speed up nodes printing
authorAriel Mashraki <ariel@mashraki.co.il>
Tue, 1 Oct 2019 19:40:20 +0000 (22:40 +0300)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 3 Oct 2019 00:16:28 +0000 (00:16 +0000)
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>

src/text/template/parse/node.go
src/text/template/parse/parse_test.go

index 61c6853679a5b7dcba657e45c6aa6512e18aa959..1c116ea6fab3dbf594306b9846dbf97f74715243 100644 (file)
@@ -28,6 +28,8 @@ type Node interface {
        // tree returns the containing *Tree.
        // It is unexported so all implementations of Node are in this package.
        tree() *Tree
+       // writeTo writes the String output to the builder.
+       writeTo(*strings.Builder)
 }
 
 // NodeType identifies the type of a parse tree node.
@@ -94,10 +96,14 @@ func (l *ListNode) tree() *Tree {
 
 func (l *ListNode) String() string {
        var sb strings.Builder
+       l.writeTo(&sb)
+       return sb.String()
+}
+
+func (l *ListNode) writeTo(sb *strings.Builder) {
        for _, n := range l.Nodes {
-               sb.WriteString(n.String())
+               n.writeTo(sb)
        }
-       return sb.String()
 }
 
 func (l *ListNode) CopyList() *ListNode {
@@ -131,6 +137,10 @@ func (t *TextNode) String() string {
        return fmt.Sprintf(textFormat, t.Text)
 }
 
+func (t *TextNode) writeTo(sb *strings.Builder) {
+       sb.WriteString(t.String())
+}
+
 func (t *TextNode) tree() *Tree {
        return t.tr
 }
@@ -160,12 +170,17 @@ func (p *PipeNode) append(command *CommandNode) {
 
 func (p *PipeNode) String() string {
        var sb strings.Builder
+       p.writeTo(&sb)
+       return sb.String()
+}
+
+func (p *PipeNode) writeTo(sb *strings.Builder) {
        if len(p.Decl) > 0 {
                for i, v := range p.Decl {
                        if i > 0 {
                                sb.WriteString(", ")
                        }
-                       sb.WriteString(v.String())
+                       v.writeTo(sb)
                }
                sb.WriteString(" := ")
        }
@@ -173,9 +188,8 @@ func (p *PipeNode) String() string {
                if i > 0 {
                        sb.WriteString(" | ")
                }
-               sb.WriteString(c.String())
+               c.writeTo(sb)
        }
-       return sb.String()
 }
 
 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 {
-       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 {
@@ -249,19 +270,23 @@ func (c *CommandNode) append(arg Node) {
 
 func (c *CommandNode) String() string {
        var sb strings.Builder
+       c.writeTo(&sb)
+       return sb.String()
+}
+
+func (c *CommandNode) writeTo(sb *strings.Builder) {
        for i, arg := range c.Args {
                if i > 0 {
                        sb.WriteByte(' ')
                }
                if arg, ok := arg.(*PipeNode); ok {
                        sb.WriteByte('(')
-                       sb.WriteString(arg.String())
+                       arg.writeTo(sb)
                        sb.WriteByte(')')
                        continue
                }
-               sb.WriteString(arg.String())
+               arg.writeTo(sb)
        }
-       return sb.String()
 }
 
 func (c *CommandNode) tree() *Tree {
@@ -312,6 +337,10 @@ func (i *IdentifierNode) String() string {
        return i.Ident
 }
 
+func (i *IdentifierNode) writeTo(sb *strings.Builder) {
+       sb.WriteString(i.String())
+}
+
 func (i *IdentifierNode) tree() *Tree {
        return i.tr
 }
@@ -335,13 +364,17 @@ func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
 
 func (v *VariableNode) String() string {
        var sb strings.Builder
+       v.writeTo(&sb)
+       return sb.String()
+}
+
+func (v *VariableNode) writeTo(sb *strings.Builder) {
        for i, id := range v.Ident {
                if i > 0 {
                        sb.WriteByte('.')
                }
                sb.WriteString(id)
        }
-       return sb.String()
 }
 
 func (v *VariableNode) tree() *Tree {
@@ -374,6 +407,10 @@ func (d *DotNode) String() string {
        return "."
 }
 
+func (d *DotNode) writeTo(sb *strings.Builder) {
+       sb.WriteString(d.String())
+}
+
 func (d *DotNode) tree() *Tree {
        return d.tr
 }
@@ -404,6 +441,10 @@ func (n *NilNode) String() string {
        return "nil"
 }
 
+func (n *NilNode) writeTo(sb *strings.Builder) {
+       sb.WriteString(n.String())
+}
+
 func (n *NilNode) tree() *Tree {
        return n.tr
 }
@@ -428,11 +469,15 @@ func (t *Tree) newField(pos Pos, ident string) *FieldNode {
 
 func (f *FieldNode) String() string {
        var sb strings.Builder
+       f.writeTo(&sb)
+       return sb.String()
+}
+
+func (f *FieldNode) writeTo(sb *strings.Builder) {
        for _, id := range f.Ident {
                sb.WriteByte('.')
                sb.WriteString(id)
        }
-       return sb.String()
 }
 
 func (f *FieldNode) tree() *Tree {
@@ -472,18 +517,22 @@ func (c *ChainNode) Add(field string) {
 
 func (c *ChainNode) String() string {
        var sb strings.Builder
+       c.writeTo(&sb)
+       return sb.String()
+}
+
+func (c *ChainNode) writeTo(sb *strings.Builder) {
        if _, ok := c.Node.(*PipeNode); ok {
                sb.WriteByte('(')
-               sb.WriteString(c.Node.String())
+               c.Node.writeTo(sb)
                sb.WriteByte(')')
        } else {
-               sb.WriteString(c.Node.String())
+               c.Node.writeTo(sb)
        }
        for _, field := range c.Field {
                sb.WriteByte('.')
                sb.WriteString(field)
        }
-       return sb.String()
 }
 
 func (c *ChainNode) tree() *Tree {
@@ -513,6 +562,10 @@ func (b *BoolNode) String() string {
        return "false"
 }
 
+func (b *BoolNode) writeTo(sb *strings.Builder) {
+       sb.WriteString(b.String())
+}
+
 func (b *BoolNode) tree() *Tree {
        return b.tr
 }
@@ -646,6 +699,10 @@ func (n *NumberNode) String() string {
        return n.Text
 }
 
+func (n *NumberNode) writeTo(sb *strings.Builder) {
+       sb.WriteString(n.String())
+}
+
 func (n *NumberNode) tree() *Tree {
        return n.tr
 }
@@ -673,6 +730,10 @@ func (s *StringNode) String() string {
        return s.Quoted
 }
 
+func (s *StringNode) writeTo(sb *strings.Builder) {
+       sb.WriteString(s.String())
+}
+
 func (s *StringNode) tree() *Tree {
        return s.tr
 }
@@ -697,6 +758,10 @@ func (e *endNode) String() string {
        return "{{end}}"
 }
 
+func (e *endNode) writeTo(sb *strings.Builder) {
+       sb.WriteString(e.String())
+}
+
 func (e *endNode) tree() *Tree {
        return e.tr
 }
@@ -725,6 +790,10 @@ func (e *elseNode) String() string {
        return "{{else}}"
 }
 
+func (e *elseNode) writeTo(sb *strings.Builder) {
+       sb.WriteString(e.String())
+}
+
 func (e *elseNode) tree() *Tree {
        return e.tr
 }
@@ -745,6 +814,12 @@ type BranchNode struct {
 }
 
 func (b *BranchNode) String() string {
+       var sb strings.Builder
+       b.writeTo(&sb)
+       return sb.String()
+}
+
+func (b *BranchNode) writeTo(sb *strings.Builder) {
        name := ""
        switch b.NodeType {
        case NodeIf:
@@ -756,10 +831,17 @@ func (b *BranchNode) String() string {
        default:
                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 {
-               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 {
@@ -833,10 +915,19 @@ func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *Temp
 }
 
 func (t *TemplateNode) String() string {
-       if t.Pipe == nil {
-               return fmt.Sprintf("{{template %q}}", t.Name)
+       var sb strings.Builder
+       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 {
index 86a100bb5f83210dae4f4851465edafa80b5b88a..4e09a7852c9c2658994ef2184c4908e7ec9d46f6 100644 (file)
@@ -304,7 +304,8 @@ var parseTests = []parseTest{
 }
 
 var builtins = map[string]interface{}{
-       "printf": fmt.Sprintf,
+       "printf":   fmt.Sprintf,
+       "contains": strings.Contains,
 }
 
 func testParse(doCopy bool, t *testing.T) {
@@ -571,7 +572,24 @@ func BenchmarkVariableString(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)
        if err != nil {
                b.Fatal(err)