From 91d3497f7d930f27fa965617c9bb2a8cd0d3841c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 5 Jun 2017 08:44:17 +0100 Subject: [PATCH 1/2] parallelise 'reduce' --- render/render.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/render/render.go b/render/render.go index 415bc3ffb..da48658cc 100644 --- a/render/render.go +++ b/render/render.go @@ -39,11 +39,25 @@ func MakeReduce(renderers ...Renderer) Renderer { // Render produces a set of Nodes given a Report. func (r *Reduce) Render(rpt report.Report, dct Decorator) report.Nodes { - result := report.Nodes{} - for _, renderer := range *r { - result = result.Merge(renderer.Render(rpt, dct)) + l := len(*r) + switch l { + case 0: + return report.Nodes{} } - return result + c := make(chan report.Nodes, l) + for _, renderer := range *r { + renderer := renderer // Pike!! + go func() { + c <- renderer.Render(rpt, dct) + }() + } + for ; l > 1; l-- { + left, right := <-c, <-c + go func() { + c <- left.Merge(right) + }() + } + return <-c } // Stats implements Renderer From 912c684e653773a43a09b86e47505ce296b65a41 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 5 Jun 2017 08:45:03 +0100 Subject: [PATCH 2/2] optimise memoisation for parallel execution don't start the same piece of work twice --- render/memoise.go | 63 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/render/memoise.go b/render/memoise.go index 08593a0c1..b39a2d3c1 100644 --- a/render/memoise.go +++ b/render/memoise.go @@ -3,20 +3,29 @@ package render import ( "fmt" "math/rand" + "sync" "github.com/bluele/gcache" "github.com/weaveworks/scope/report" ) +// renderCache is keyed on the combination of Memoiser and report +// id. It contains promises of report.Nodes, which result from +// rendering the report with the Memoiser's renderer. +// +// The use of promises ensures that in the absence of cache evictions +// a memoiser will only ever render a report once, even when Render() +// is invoked concurrently. var renderCache = gcache.New(100).LRU().Build() type memoise struct { + sync.Mutex Renderer id string } -// Memoise wraps the renderer in a loving embrace of caching +// Memoise wraps the renderer in a loving embrace of caching. func Memoise(r Renderer) Renderer { return &memoise{ Renderer: r, @@ -24,23 +33,55 @@ func Memoise(r Renderer) Renderer { } } -// Render produces a set of Nodes given a Report. -// Ideally, it just retrieves it from the cache, otherwise it calls through to -// `r` and stores the result. +// Render produces a set of Nodes given a Report. Ideally, it just +// retrieves a promise from the cache and returns its value, otherwise +// it stores a new promise and fulfils it by calling through to +// m.Renderer. +// +// The cache is bypassed when rendering a report with a decorator. func (m *memoise) Render(rpt report.Report, dct Decorator) report.Nodes { + if dct != nil { + return m.Renderer.Render(rpt, dct) + } + key := fmt.Sprintf("%s-%s", rpt.ID, m.id) - if dct == nil { - if result, err := renderCache.Get(key); err == nil { - return result.(report.Nodes) - } + + m.Lock() + v, err := renderCache.Get(key) + if err == nil { + m.Unlock() + return v.(*promise).Get() } + promise := newPromise() + renderCache.Set(key, promise) + m.Unlock() + output := m.Renderer.Render(rpt, dct) - if dct == nil { - renderCache.Set(key, output) - } + + promise.Set(output) + return output } +type promise struct { + val report.Nodes + done chan struct{} +} + +func newPromise() *promise { + return &promise{done: make(chan struct{})} +} + +func (p *promise) Set(val report.Nodes) { + p.val = val + close(p.done) +} + +func (p *promise) Get() report.Nodes { + <-p.done + return p.val +} + // ResetCache blows away the rendered node cache. func ResetCache() { renderCache.Purge()