shithub: hugo

Download patch

ref: 6178238a0b069ae8ce65a23e3dd60c091de0cfef
parent: df953839143c15e147d35f8908ed7f02fb62a10a
author: Bjørn Erik Pedersen <[email protected]>
date: Sat Mar 18 12:46:10 EDT 2017

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%
```

--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -190,6 +190,8 @@
 	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 @@
 	}
 
 	return p.s.layoutHandler.For(
-		p.createLayoutDescriptor(),
+		p.layoutDescriptor,
 		layoutOverride,
 		output.HTMLType)
 }
@@ -880,6 +882,7 @@
 	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) 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
 }
 
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -1656,7 +1656,7 @@
 }
 
 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 {
--- a/output/layout.go
+++ b/output/layout.go
@@ -17,6 +17,7 @@
 	"fmt"
 	"path"
 	"strings"
+	"sync"
 )
 
 // LayoutDescriptor describes how a layout should be chosen. This is
@@ -32,10 +33,19 @@
 // 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 @@
 )
 
 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
@@ -109,6 +129,10 @@
 
 		return layoutsWithThemeLayouts
 	}
+
+	l.mu.Lock()
+	l.cache[key] = layouts
+	l.mu.Unlock()
 
 	return layouts
 }
--- a/output/layout_test.go
+++ b/output/layout_test.go
@@ -73,4 +73,15 @@
 			}
 		})
 	}
+
+}
+
+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)
+	}
 }