shithub: hugo

Download patch

ref: 9af47f07d3f9700e233a98d7e84d85c3a30f9cd5
parent: f4cb8e1688b1459472413f3c85cb3b6297397020
author: Noah Campbell <[email protected]>
date: Thu Oct 31 18:14:11 EDT 2013

Improve rendering time

50% speedup.  Fix #91

to run the benchmark:

		go test -test.run=NONE -bench=".*" -test.benchmem=true ./transform/ > new.txt

to compare the results:

		/usr/local/go/misc/benchcmp baseline.txt new.txt

Speedup and memory improvements

		benchmark             old ns/op    new ns/op    delta
		BenchmarkChain           101219        50453  -50.15%
		BenchmarkTransform        51625        45531  -11.80%

		benchmark            old allocs   new allocs    delta
		BenchmarkChain              222          103  -53.60%
		BenchmarkTransform          135          106  -21.48%

		benchmark             old bytes    new bytes    delta
		BenchmarkChain            23919        10998  -54.02%
		BenchmarkTransform        11858        10665  -10.06%

--- a/baseline.txt
+++ b/baseline.txt
@@ -1,4 +1,4 @@
 PASS
-BenchmarkChain	   10000	    101219 ns/op	   23919 B/op	     222 allocs/op
-BenchmarkTransform	   50000	     51625 ns/op	   11858 B/op	     135 allocs/op
-ok  	github.com/spf13/hugo/transform	4.172s
+BenchmarkChain	   50000	     50453 ns/op	   10998 B/op	     103 allocs/op
+BenchmarkTransform	   50000	     45531 ns/op	   10665 B/op	     106 allocs/op
+ok  	github.com/spf13/hugo/transform	5.904s
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -64,7 +64,6 @@
 	Info        SiteInfo
 	Shortcodes  map[string]ShortcodeFunc
 	timer       *nitro.B
-	Transformer transform.Transformer
 	Target      target.Output
 	Alias       target.AliasPublisher
 	Completed   chan bool
@@ -581,9 +580,14 @@
 		section, _ = page.RelPermalink()
 	}
 
+
+	absURL, err := transform.AbsURL(s.Config.BaseUrl)
+	if err != nil {
+		return
+	}
+
 	transformer := transform.NewChain(
-		&transform.AbsURL{BaseURL: s.Config.BaseUrl},
-		&transform.NavActive{Section: section},
+		append(absURL, transform.NavActive(section, "hugo-nav")...)...
 	)
 
 	renderReader, renderWriter := io.Pipe()
--- a/transform/absurl.go
+++ b/transform/absurl.go
@@ -2,26 +2,20 @@
 
 import (
 	htmltran "code.google.com/p/go-html-transform/html/transform"
-	"io"
 	"net/url"
 )
 
-type AbsURL struct {
-	BaseURL string
-}
+func AbsURL(absURL string) (trs []*htmltran.Transform, err error) {
+	var baseURL *url.URL
 
-func (t *AbsURL) Apply(w io.Writer, r io.Reader) (err error) {
-	var tr *htmltran.Transformer
-
-	if tr, err = htmltran.NewFromReader(r); err != nil {
+	if baseURL, err = url.Parse(absURL); err != nil {
 		return
 	}
 
-	if err = t.absUrlify(tr, elattr{"a", "href"}, elattr{"script", "src"}); err != nil {
+	if trs, err = absUrlify(baseURL, elattr{"a", "href"}, elattr{"script", "src"}); err != nil {
 		return
 	}
-
-	return tr.Render(w)
+	return
 }
 
 type elattr struct {
@@ -28,13 +22,9 @@
 	tag, attr string
 }
 
-func (t *AbsURL) absUrlify(tr *htmltran.Transformer, selectors ...elattr) (err error) {
-	var baseURL, inURL *url.URL
+func absUrlify(baseURL *url.URL, selectors ...elattr) (trs []*htmltran.Transform, err error) {
+	var inURL *url.URL
 
-	if baseURL, err = url.Parse(t.BaseURL); err != nil {
-		return
-	}
-
 	replace := func(in string) string {
 		if inURL, err = url.Parse(in); err != nil {
 			return in + "?"
@@ -46,9 +36,8 @@
 	}
 
 	for _, el := range selectors {
-		if err = tr.Apply(htmltran.TransformAttrib(el.attr, replace), el.tag); err != nil {
-			return
-		}
+		mt := htmltran.MustTrans(htmltran.TransformAttrib(el.attr, replace), el.tag)
+		trs = append(trs, mt)
 	}
 
 	return
--- a/transform/chain.go
+++ b/transform/chain.go
@@ -1,29 +1,25 @@
 package transform
 
 import (
-	"bytes"
+	htmltran "code.google.com/p/go-html-transform/html/transform"
 	"io"
 )
 
-type chain struct {
-	transformers []Transformer
-}
+type chain []*htmltran.Transform
 
-func NewChain(trs ...Transformer) Transformer {
-	return &chain{transformers: trs}
+func NewChain(trs ...*htmltran.Transform) chain {
+	return trs
 }
 
 func (c *chain) Apply(w io.Writer, r io.Reader) (err error) {
-	in := r
-	for _, tr := range c.transformers {
-		out := new(bytes.Buffer)
-		err = tr.Apply(out, in)
-		if err != nil {
-			return
-		}
-		in = bytes.NewBuffer(out.Bytes())
+
+	var tr *htmltran.Transformer
+
+	if tr, err = htmltran.NewFromReader(r); err != nil {
+		return
 	}
 
-	_, err = io.Copy(w, in)
-	return
+	tr.ApplyAll(*c...)
+
+	return tr.Render(w)
 }
--- a/transform/chain_test.go
+++ b/transform/chain_test.go
@@ -15,7 +15,8 @@
 }
 
 func TestChainOneTransformer(t *testing.T) {
-	tr := NewChain(&AbsURL{BaseURL: "http://base"})
+	absURL, _ := AbsURL("http://base")
+	tr := NewChain(absURL...)
 	apply(t.Errorf, tr, abs_url_tests)
 }
 
@@ -28,19 +29,19 @@
 }
 
 func TestChainTwoTransformer(t *testing.T) {
-	tr := NewChain(
-		&AbsURL{BaseURL: "http://two"},
-		&NavActive{Section: "section_1"},
-	)
+	absURL, _ := AbsURL("http://two")
+	nav := NavActive("section_1", "hugo-nav")
+	tr := NewChain(append(absURL, nav...)...)
 	apply(t.Errorf, tr, two_chain_tests)
 }
 
 func BenchmarkChain(b *testing.B) {
 
-	tr := NewChain(
-		&AbsURL{BaseURL: "http://two"},
-		&NavActive{Section: "section_1"},
-	)
+	absURL, _ := AbsURL("http://two")
+	nav := NavActive("section_1", "hugo-nav")
+	tr := NewChain(append(absURL, nav...)...)
+
+	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 		apply(b.Errorf, tr, two_chain_tests)
 	}
--- a/transform/nav.go
+++ b/transform/nav.go
@@ -3,34 +3,10 @@
 import (
 	htmltran "code.google.com/p/go-html-transform/html/transform"
 	"fmt"
-	"io"
 )
 
-type NavActive struct {
-	Section  string
-	AttrName string
-}
-
-func (n *NavActive) Apply(w io.Writer, r io.Reader) (err error) {
-	var tr *htmltran.Transformer
-
-	if n.Section == "" {
-		_, err = io.Copy(w, r)
-		return
-	}
-
-	if tr, err = htmltran.NewFromReader(r); err != nil {
-		return
-	}
-
-	if n.AttrName == "" {
-		n.AttrName = "hugo-nav"
-	}
-
-	err = tr.Apply(htmltran.ModifyAttrib("class", "active"), fmt.Sprintf("li[%s=%s]", n.AttrName, n.Section))
-	if err != nil {
-		return
-	}
-
-	return tr.Render(w)
+func NavActive(section, attrName string) (tr []*htmltran.Transform) {
+	ma := htmltran.MustTrans(htmltran.ModifyAttrib("class", "active"), fmt.Sprintf("li[%s=%s]", attrName, section))
+	tr = append(tr, ma)
+	return
 }
--- a/transform/nav_test.go
+++ b/transform/nav_test.go
@@ -31,25 +31,11 @@
 
 </body></html>`
 
-func TestDegenerateNoSectionSet(t *testing.T) {
-	var (
-		tr  = new(NavActive)
-		out = new(bytes.Buffer)
-	)
-
-	if err := tr.Apply(out, strings.NewReader(HTML_WITH_NAV)); err != nil {
-		t.Errorf("Unexpected error in NavActive.Apply: %s", err)
-	}
-
-	if out.String() != HTML_WITH_NAV {
-		t.Errorf("NavActive.Apply should simply pass along the buffer unmodified.")
-	}
-}
-
 func TestSetNav(t *testing.T) {
-	tr := &NavActive{Section: "section_2"}
+	trs := NavActive("section_2", "hugo-nav")
+	chain := NewChain(trs...)
 	out := new(bytes.Buffer)
-	if err := tr.Apply(out, strings.NewReader(HTML_WITH_NAV)); err != nil {
+	if err := chain.Apply(out, strings.NewReader(HTML_WITH_NAV)); err != nil {
 		t.Errorf("Unexpected error in Apply() for NavActive: %s", err)
 	}
 
@@ -60,11 +46,13 @@
 }
 
 func BenchmarkTransform(b *testing.B) {
+	tr := NavActive("section_2", "hugo-nav")
+	chain := NewChain(tr...)
+	out := new(bytes.Buffer)
 	for i := 0; i < b.N; i++ {
-		tr := &NavActive{Section: "section_2"}
-		out := new(bytes.Buffer)
-		if err := tr.Apply(out, strings.NewReader(HTML_WITH_NAV)); err != nil {
+		if err := chain.Apply(out, strings.NewReader(HTML_WITH_NAV)); err != nil {
 			b.Errorf("Unexpected error in Apply() for NavActive: %s", err)
 		}
+		out.Reset()
 	}
 }
--- a/transform/post.go
+++ b/transform/post.go
@@ -1,9 +1,1 @@
 package transform
-
-import (
-	"io"
-)
-
-type Transformer interface {
-	Apply(io.Writer, io.Reader) error
-}
--- a/transform/posttrans_test.go
+++ b/transform/posttrans_test.go
@@ -16,12 +16,9 @@
 const CORRECT_OUTPUT_SRC_HREF = "<!DOCTYPE html><html><head><script src=\"http://base/foobar.js\"></script></head><body><nav><h1>title</h1></nav><article>content <a href=\"http://base/foobar\">foobar</a>. Follow up</article></body></html>"
 
 func TestAbsUrlify(t *testing.T) {
-
-	tr := &AbsURL{
-		BaseURL: "http://base",
-	}
-
-	apply(t.Errorf, tr, abs_url_tests)
+	tr, _ := AbsURL("http://base")
+	chain := NewChain(tr...)
+	apply(t.Errorf, chain, abs_url_tests)
 }
 
 type test struct {
@@ -35,9 +32,9 @@
 	{H5_JS_CONTENT_ABS_URL, H5_JS_CONTENT_ABS_URL},
 }
 
-type errorf func (string, ...interface{})
+type errorf func(string, ...interface{})
 
-func apply(ef errorf, tr Transformer, tests []test) {
+func apply(ef errorf, tr chain, tests []test) {
 	for _, test := range tests {
 		out := new(bytes.Buffer)
 		err := tr.Apply(out, strings.NewReader(test.content))