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%
This commit is contained in:
Noah Campbell 2013-10-31 22:14:11 -07:00
parent f4cb8e1688
commit 9af47f07d3
9 changed files with 58 additions and 115 deletions

View file

@ -1,4 +1,4 @@
PASS PASS
BenchmarkChain 10000 101219 ns/op 23919 B/op 222 allocs/op BenchmarkChain 50000 50453 ns/op 10998 B/op 103 allocs/op
BenchmarkTransform 50000 51625 ns/op 11858 B/op 135 allocs/op BenchmarkTransform 50000 45531 ns/op 10665 B/op 106 allocs/op
ok github.com/spf13/hugo/transform 4.172s ok github.com/spf13/hugo/transform 5.904s

View file

@ -64,7 +64,6 @@ type Site struct {
Info SiteInfo Info SiteInfo
Shortcodes map[string]ShortcodeFunc Shortcodes map[string]ShortcodeFunc
timer *nitro.B timer *nitro.B
Transformer transform.Transformer
Target target.Output Target target.Output
Alias target.AliasPublisher Alias target.AliasPublisher
Completed chan bool Completed chan bool
@ -581,9 +580,14 @@ func (s *Site) render(d interface{}, out string, layouts ...string) (err error)
section, _ = page.RelPermalink() section, _ = page.RelPermalink()
} }
absURL, err := transform.AbsURL(s.Config.BaseUrl)
if err != nil {
return
}
transformer := transform.NewChain( transformer := transform.NewChain(
&transform.AbsURL{BaseURL: s.Config.BaseUrl}, append(absURL, transform.NavActive(section, "hugo-nav")...)...
&transform.NavActive{Section: section},
) )
renderReader, renderWriter := io.Pipe() renderReader, renderWriter := io.Pipe()

View file

@ -2,38 +2,28 @@ package transform
import ( import (
htmltran "code.google.com/p/go-html-transform/html/transform" htmltran "code.google.com/p/go-html-transform/html/transform"
"io"
"net/url" "net/url"
) )
type AbsURL struct { func AbsURL(absURL string) (trs []*htmltran.Transform, err error) {
BaseURL string var baseURL *url.URL
}
func (t *AbsURL) Apply(w io.Writer, r io.Reader) (err error) { if baseURL, err = url.Parse(absURL); err != nil {
var tr *htmltran.Transformer
if tr, err = htmltran.NewFromReader(r); err != nil {
return 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
} }
return
return tr.Render(w)
} }
type elattr struct { type elattr struct {
tag, attr string tag, attr string
} }
func (t *AbsURL) absUrlify(tr *htmltran.Transformer, selectors ...elattr) (err error) { func absUrlify(baseURL *url.URL, selectors ...elattr) (trs []*htmltran.Transform, err error) {
var baseURL, inURL *url.URL var inURL *url.URL
if baseURL, err = url.Parse(t.BaseURL); err != nil {
return
}
replace := func(in string) string { replace := func(in string) string {
if inURL, err = url.Parse(in); err != nil { if inURL, err = url.Parse(in); err != nil {
@ -46,9 +36,8 @@ func (t *AbsURL) absUrlify(tr *htmltran.Transformer, selectors ...elattr) (err e
} }
for _, el := range selectors { for _, el := range selectors {
if err = tr.Apply(htmltran.TransformAttrib(el.attr, replace), el.tag); err != nil { mt := htmltran.MustTrans(htmltran.TransformAttrib(el.attr, replace), el.tag)
return trs = append(trs, mt)
}
} }
return return

View file

@ -1,29 +1,25 @@
package transform package transform
import ( import (
"bytes" htmltran "code.google.com/p/go-html-transform/html/transform"
"io" "io"
) )
type chain struct { type chain []*htmltran.Transform
transformers []Transformer
}
func NewChain(trs ...Transformer) Transformer { func NewChain(trs ...*htmltran.Transform) chain {
return &chain{transformers: trs} return trs
} }
func (c *chain) Apply(w io.Writer, r io.Reader) (err error) { func (c *chain) Apply(w io.Writer, r io.Reader) (err error) {
in := r
for _, tr := range c.transformers { var tr *htmltran.Transformer
out := new(bytes.Buffer)
err = tr.Apply(out, in) if tr, err = htmltran.NewFromReader(r); err != nil {
if err != nil { return
return
}
in = bytes.NewBuffer(out.Bytes())
} }
_, err = io.Copy(w, in) tr.ApplyAll(*c...)
return
return tr.Render(w)
} }

View file

@ -15,7 +15,8 @@ func TestChainZeroTransformers(t *testing.T) {
} }
func TestChainOneTransformer(t *testing.T) { 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) apply(t.Errorf, tr, abs_url_tests)
} }
@ -28,19 +29,19 @@ var two_chain_tests = []test{
} }
func TestChainTwoTransformer(t *testing.T) { func TestChainTwoTransformer(t *testing.T) {
tr := NewChain( absURL, _ := AbsURL("http://two")
&AbsURL{BaseURL: "http://two"}, nav := NavActive("section_1", "hugo-nav")
&NavActive{Section: "section_1"}, tr := NewChain(append(absURL, nav...)...)
)
apply(t.Errorf, tr, two_chain_tests) apply(t.Errorf, tr, two_chain_tests)
} }
func BenchmarkChain(b *testing.B) { func BenchmarkChain(b *testing.B) {
tr := NewChain( absURL, _ := AbsURL("http://two")
&AbsURL{BaseURL: "http://two"}, nav := NavActive("section_1", "hugo-nav")
&NavActive{Section: "section_1"}, tr := NewChain(append(absURL, nav...)...)
)
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
apply(b.Errorf, tr, two_chain_tests) apply(b.Errorf, tr, two_chain_tests)
} }

View file

@ -3,34 +3,10 @@ package transform
import ( import (
htmltran "code.google.com/p/go-html-transform/html/transform" htmltran "code.google.com/p/go-html-transform/html/transform"
"fmt" "fmt"
"io"
) )
type NavActive struct { func NavActive(section, attrName string) (tr []*htmltran.Transform) {
Section string ma := htmltran.MustTrans(htmltran.ModifyAttrib("class", "active"), fmt.Sprintf("li[%s=%s]", attrName, section))
AttrName string tr = append(tr, ma)
} return
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)
} }

View file

@ -31,25 +31,11 @@ const EXPECTED_HTML_WITH_NAV_1 = `<!DOCTYPE html><html><head></head>
</body></html>` </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) { func TestSetNav(t *testing.T) {
tr := &NavActive{Section: "section_2"} trs := NavActive("section_2", "hugo-nav")
chain := NewChain(trs...)
out := new(bytes.Buffer) 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) t.Errorf("Unexpected error in Apply() for NavActive: %s", err)
} }
@ -60,11 +46,13 @@ func TestSetNav(t *testing.T) {
} }
func BenchmarkTransform(b *testing.B) { 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++ { for i := 0; i < b.N; i++ {
tr := &NavActive{Section: "section_2"} if err := chain.Apply(out, strings.NewReader(HTML_WITH_NAV)); err != nil {
out := new(bytes.Buffer)
if err := tr.Apply(out, strings.NewReader(HTML_WITH_NAV)); err != nil {
b.Errorf("Unexpected error in Apply() for NavActive: %s", err) b.Errorf("Unexpected error in Apply() for NavActive: %s", err)
} }
out.Reset()
} }
} }

View file

@ -1,9 +1 @@
package transform package transform
import (
"io"
)
type Transformer interface {
Apply(io.Writer, io.Reader) error
}

View file

@ -16,12 +16,9 @@ const H5_JS_CONTENT_ABS_URL = "<!DOCTYPE html><html><head><script src=\"http://u
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>" 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) { func TestAbsUrlify(t *testing.T) {
tr, _ := AbsURL("http://base")
tr := &AbsURL{ chain := NewChain(tr...)
BaseURL: "http://base", apply(t.Errorf, chain, abs_url_tests)
}
apply(t.Errorf, tr, abs_url_tests)
} }
type test struct { type test struct {
@ -35,9 +32,9 @@ var abs_url_tests = []test{
{H5_JS_CONTENT_ABS_URL, H5_JS_CONTENT_ABS_URL}, {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 { for _, test := range tests {
out := new(bytes.Buffer) out := new(bytes.Buffer)
err := tr.Apply(out, strings.NewReader(test.content)) err := tr.Apply(out, strings.NewReader(test.content))