Skip to content

Commit

Permalink
Limit number of parallel frames being painted (#365)
Browse files Browse the repository at this point in the history
In #67, we started painting frames in parallel instead of serially, in
order to fully utilize available CPU.

But we did this without any limit. So if a `Root` had hundreds of frames,
we would attempt to paint all of them in parallel. This would consume
huge amounts of memory by allocating hundreds or thousands of image
buffers.

Instead, we now limit parallelism to the number of CPU's.
  • Loading branch information
rohansingh authored Aug 11, 2022
1 parent 8895f3c commit ff21c2c
Showing 1 changed file with 35 additions and 2 deletions.
37 changes: 35 additions & 2 deletions render/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package render
import (
"image"
"image/color"
"runtime"
"sync"

"github.com/tidbyt/gg"
Expand Down Expand Up @@ -38,18 +39,51 @@ type Root struct {
Child Widget `starlark:"child,required"`
Delay int32 `starlark:"delay"`
MaxAge int32 `starlark:"max_age"`

maxParallelFrames int
}

type RootPaintOption func(*Root)

// WithMaxParallelFrames sets the maximum number of frames that will
// be painted in parallel.
//
// By default, only `runtime.NumCPU()` frames are painted in parallel.
// Higher parallelism consumes more memory, and doesn't usually make
// sense since painting is CPU-bouond.
func WithMaxParallelFrames(max int) RootPaintOption {
return func(r *Root) {
r.maxParallelFrames = max
}
}

// Paint renders the child widget onto the frame. It doesn't do
// any resizing or alignment.
func (r Root) Paint(solidBackground bool) []image.Image {
func (r Root) Paint(solidBackground bool, opts ...RootPaintOption) []image.Image {
for _, opt := range opts {
opt(&r)
}

numFrames := r.Child.FrameCount()
frames := make([]image.Image, numFrames)

parallelism := r.maxParallelFrames
if parallelism <= 0 {
parallelism = runtime.NumCPU()
}

var wg sync.WaitGroup
sem := make(chan bool, parallelism)
for i := 0; i < numFrames; i++ {
wg.Add(1)
sem <- true

go func(i int) {
defer func() {
<-sem
wg.Done()
}()

dc := gg.NewContext(DefaultFrameWidth, DefaultFrameHeight)
if solidBackground {
dc.SetColor(color.Black)
Expand All @@ -60,7 +94,6 @@ func (r Root) Paint(solidBackground bool) []image.Image {
r.Child.Paint(dc, image.Rect(0, 0, DefaultFrameWidth, DefaultFrameHeight), i)
dc.Pop()
frames[i] = dc.Image()
wg.Done()
}(i)
}

Expand Down

0 comments on commit ff21c2c

Please sign in to comment.