mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
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:
parent
f4cb8e1688
commit
9af47f07d3
9 changed files with 58 additions and 115 deletions
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1 @@
|
||||||
package transform
|
package transform
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Transformer interface {
|
|
||||||
Apply(io.Writer, io.Reader) error
|
|
||||||
}
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in a new issue