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%
```
This commit is contained in:
Bjørn Erik Pedersen 2017-03-18 16:46:10 +01:00
parent df95383914
commit 6178238a0b
4 changed files with 42 additions and 4 deletions

View file

@ -190,6 +190,8 @@ type Page struct {
permalink string permalink string
relPermalink string relPermalink string
layoutDescriptor output.LayoutDescriptor
scratch *Scratch scratch *Scratch
// It would be tempting to use the language set on the Site, but in they way we do // 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( return p.s.layoutHandler.For(
p.createLayoutDescriptor(), p.layoutDescriptor,
layoutOverride, layoutOverride,
output.HTMLType) output.HTMLType)
} }
@ -880,6 +882,7 @@ func (p *Page) initURLs() error {
p.permalink = p.s.permalink(rel) p.permalink = p.s.permalink(rel)
rel = p.s.PathSpec.PrependBasePath(rel) rel = p.s.PathSpec.PrependBasePath(rel)
p.relPermalink = rel p.relPermalink = rel
p.layoutDescriptor = p.createLayoutDescriptor()
return nil return nil
} }
@ -1558,7 +1561,7 @@ func (p *Page) Hugo() *HugoInfo {
func (p *Page) RSSlink() template.URL { func (p *Page) RSSlink() template.URL {
// TODO(bep) we cannot have two of these // TODO(bep) we cannot have two of these
// Remove in Hugo 0.20 // Remove in Hugo 0.20
helpers.Deprecated(".Page", "Use RSSlink", "RSSLink", true) helpers.Deprecated(".Page", "RSSlink", "Use RSSLink", true)
return p.RSSLink return p.RSSLink
} }

View file

@ -1656,7 +1656,7 @@ func (s *Site) kindFromSections(sections []string) string {
} }
func (s *Site) layouts(p *PageOutput) []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 { func (s *Site) preparePages() error {

View file

@ -17,6 +17,7 @@ import (
"fmt" "fmt"
"path" "path"
"strings" "strings"
"sync"
) )
// LayoutDescriptor describes how a layout should be chosen. This is // LayoutDescriptor describes how a layout should be chosen. This is
@ -32,10 +33,19 @@ type LayoutDescriptor struct {
// TODO(bep) output improve names // TODO(bep) output improve names
type LayoutHandler struct { type LayoutHandler struct {
hasTheme bool hasTheme bool
mu sync.RWMutex
cache map[layoutCacheKey][]string
}
type layoutCacheKey struct {
d LayoutDescriptor
layoutOverride string
f Format
} }
func NewLayoutHandler(hasTheme bool) *LayoutHandler { func NewLayoutHandler(hasTheme bool) *LayoutHandler {
return &LayoutHandler{hasTheme: hasTheme} return &LayoutHandler{hasTheme: hasTheme, cache: make(map[layoutCacheKey][]string)}
} }
const ( const (
@ -62,6 +72,16 @@ indexes/indexes.NAME.SUFFIX indexes/indexes.SUFFIX
) )
func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format) []string { 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 var layouts []string
layout := d.Layout layout := d.Layout
@ -110,6 +130,10 @@ func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format)
return layoutsWithThemeLayouts return layoutsWithThemeLayouts
} }
l.mu.Lock()
l.cache[key] = layouts
l.mu.Unlock()
return layouts return layouts
} }

View file

@ -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)
}
} }