From 6178238a0b069ae8ce65a23e3dd60c091de0cfef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sat, 18 Mar 2017 16:46:10 +0100 Subject: [PATCH] output: Speed up layout calculations ``` BenchmarkLayout-4 4883 497 -89.82% benchmark old allocs new allocs delta BenchmarkLayout-4 18 1 -94.44% benchmark old bytes new bytes delta BenchmarkLayout-4 1624 32 -98.03% ``` --- hugolib/page.go | 7 +++++-- hugolib/site.go | 2 +- output/layout.go | 26 +++++++++++++++++++++++++- output/layout_test.go | 11 +++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/hugolib/page.go b/hugolib/page.go index cad9b398b..1308aa49d 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -190,6 +190,8 @@ type Page struct { permalink string relPermalink string + layoutDescriptor output.LayoutDescriptor + scratch *Scratch // It would be tempting to use the language set on the Site, but in they way we do @@ -666,7 +668,7 @@ func (p *Page) layouts(layouts ...string) []string { } return p.s.layoutHandler.For( - p.createLayoutDescriptor(), + p.layoutDescriptor, layoutOverride, output.HTMLType) } @@ -880,6 +882,7 @@ func (p *Page) initURLs() error { p.permalink = p.s.permalink(rel) rel = p.s.PathSpec.PrependBasePath(rel) p.relPermalink = rel + p.layoutDescriptor = p.createLayoutDescriptor() return nil } @@ -1558,7 +1561,7 @@ func (p *Page) Hugo() *HugoInfo { func (p *Page) RSSlink() template.URL { // TODO(bep) we cannot have two of these // Remove in Hugo 0.20 - helpers.Deprecated(".Page", "Use RSSlink", "RSSLink", true) + helpers.Deprecated(".Page", "RSSlink", "Use RSSLink", true) return p.RSSLink } diff --git a/hugolib/site.go b/hugolib/site.go index 0a90290e0..0959df3d5 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -1656,7 +1656,7 @@ func (s *Site) kindFromSections(sections []string) string { } func (s *Site) layouts(p *PageOutput) []string { - return s.layoutHandler.For(p.createLayoutDescriptor(), "", p.outputFormat) + return s.layoutHandler.For(p.layoutDescriptor, "", p.outputFormat) } func (s *Site) preparePages() error { diff --git a/output/layout.go b/output/layout.go index c33ce3f0c..833a6cf4a 100644 --- a/output/layout.go +++ b/output/layout.go @@ -17,6 +17,7 @@ import ( "fmt" "path" "strings" + "sync" ) // LayoutDescriptor describes how a layout should be chosen. This is @@ -32,10 +33,19 @@ type LayoutDescriptor struct { // TODO(bep) output improve names type LayoutHandler struct { hasTheme bool + + mu sync.RWMutex + cache map[layoutCacheKey][]string +} + +type layoutCacheKey struct { + d LayoutDescriptor + layoutOverride string + f Format } func NewLayoutHandler(hasTheme bool) *LayoutHandler { - return &LayoutHandler{hasTheme: hasTheme} + return &LayoutHandler{hasTheme: hasTheme, cache: make(map[layoutCacheKey][]string)} } const ( @@ -62,6 +72,16 @@ indexes/indexes.NAME.SUFFIX indexes/indexes.SUFFIX ) func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format) []string { + + // We will get lots of requests for the same layouts, so avoid recalculations. + key := layoutCacheKey{d, layoutOverride, f} + l.mu.RLock() + if cacheVal, found := l.cache[key]; found { + l.mu.RUnlock() + return cacheVal + } + l.mu.RUnlock() + var layouts []string layout := d.Layout @@ -110,6 +130,10 @@ func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format) return layoutsWithThemeLayouts } + l.mu.Lock() + l.cache[key] = layouts + l.mu.Unlock() + return layouts } diff --git a/output/layout_test.go b/output/layout_test.go index e678197ca..aa0657a36 100644 --- a/output/layout_test.go +++ b/output/layout_test.go @@ -73,4 +73,15 @@ func TestLayout(t *testing.T) { } }) } + +} + +func BenchmarkLayout(b *testing.B) { + descriptor := LayoutDescriptor{Kind: "taxonomyTerm", Section: "categories"} + l := NewLayoutHandler(false) + + for i := 0; i < b.N; i++ { + layouts := l.For(descriptor, "", HTMLType) + require.NotEmpty(b, layouts) + } }