From cc247962703eba99eae732876496375191f16cbe Mon Sep 17 00:00:00 2001 From: divVerent Date: Sat, 8 Apr 2023 10:31:22 -0700 Subject: [PATCH] internal/restorable: optimize removeDuplicatedRegions from O((n+m)^2) to O(n*m + m^2) (#2631) This is achieved by replacing the function by one that only adds a single new region, and only considers duplicates between the previously existing region and the one newly added one, thereby removing previously redundant checking of each previously existing region against each other. This speeds up AAAAXY loading on a Moto G7 Play from 52.27 seconds to 8.15 seconds. Closes #2626 --- internal/restorable/export_test.go | 4 +- internal/restorable/image.go | 76 ++++++++++++---------------- internal/restorable/rect_test.go | 79 +++++++++++++++++++++--------- 3 files changed, 91 insertions(+), 68 deletions(-) diff --git a/internal/restorable/export_test.go b/internal/restorable/export_test.go index b25c1b11b..aad181758 100644 --- a/internal/restorable/export_test.go +++ b/internal/restorable/export_test.go @@ -18,6 +18,6 @@ import ( "image" ) -func RemoveDuplicatedRegions(regions []image.Rectangle) int { - return removeDuplicatedRegions(regions) +func AppendRegionRemovingDuplicates(regions *[]image.Rectangle, region image.Rectangle) { + appendRegionRemovingDuplicates(regions, region) } diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 14bd6019e..6a0658f1f 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -240,26 +240,26 @@ func (i *Image) makeStale(rect image.Rectangle) { return } - origNum := len(i.staleRegions) - i.staleRegions = i.appendRegionsForDrawTriangles(i.staleRegions) + var addedRegions []image.Rectangle + i.appendRegionsForDrawTriangles(&addedRegions) if !rect.Empty() { - i.staleRegions = append(i.staleRegions, rect) + appendRegionRemovingDuplicates(&addedRegions, rect) + } + + for _, rect := range addedRegions { + appendRegionRemovingDuplicates(&i.staleRegions, rect) } i.clearDrawTrianglesHistory() // Clear pixels to save memory. - for _, r := range i.staleRegions[origNum:] { + for _, r := range addedRegions { if r.Empty() { continue } i.basePixels.Clear(r.Min.X, r.Min.Y, r.Dx(), r.Dy()) } - // Remove duplicated regions to avoid unnecessary reading pixels from GPU. - n := removeDuplicatedRegions(i.staleRegions) - i.staleRegions = i.staleRegions[:n] - // Don't have to call makeStale recursively here. // Restoring is done after topological sorting is done. // If an image depends on another stale image, this means that @@ -483,12 +483,11 @@ func (i *Image) readPixelsFromGPU(graphicsDriver graphicsdriver.Graphics) error if i.stale { rs = i.staleRegions } else { - i.regionsCache = i.appendRegionsForDrawTriangles(i.regionsCache) + i.appendRegionsForDrawTriangles(&i.regionsCache) defer func() { i.regionsCache = i.regionsCache[:0] }() - n := removeDuplicatedRegions(i.regionsCache) - rs = i.regionsCache[:n] + rs = i.regionsCache } for _, r := range rs { @@ -625,14 +624,12 @@ func (i *Image) restore(graphicsDriver graphicsdriver.Graphics) error { // In order to clear the draw-triangles history, read pixels from GPU. if len(i.drawTrianglesHistory) > 0 { - i.regionsCache = i.appendRegionsForDrawTriangles(i.regionsCache) + i.appendRegionsForDrawTriangles(&i.regionsCache) defer func() { i.regionsCache = i.regionsCache[:0] }() - n := removeDuplicatedRegions(i.regionsCache) - rs := i.regionsCache[:n] - for _, r := range rs { + for _, r := range i.regionsCache { if r.Empty() { continue } @@ -698,18 +695,14 @@ func (i *Image) InternalSize() (int, int) { return i.image.InternalSize() } -func (i *Image) appendRegionsForDrawTriangles(regions []image.Rectangle) []image.Rectangle { - n := len(regions) +func (i *Image) appendRegionsForDrawTriangles(regions *[]image.Rectangle) { for _, d := range i.drawTrianglesHistory { r := regionToRectangle(d.dstRegion) if r.Empty() { continue } - regions = append(regions, r) + appendRegionRemovingDuplicates(regions, r) } - - nn := removeDuplicatedRegions(regions[n:]) - return regions[:n+nn] } func regionToRectangle(region graphicsdriver.Region) image.Rectangle { @@ -720,34 +713,29 @@ func regionToRectangle(region graphicsdriver.Region) image.Rectangle { int(math.Ceil(float64(region.Y+region.Height)))) } -// removeDuplicatedRegions removes duplicated regions and returns the new size of the slice. -// If a region covers other regions, the covered regions are removed. -func removeDuplicatedRegions(regions []image.Rectangle) int { - for i, r := range regions { - if r.Empty() { - continue - } - for j, rr := range regions { - if i == j { - continue - } - if rr.Empty() { - continue - } - if rr.In(r) { - regions[j] = image.Rectangle{} - } +// appendRegionRemovingDuplicates adds a region to a given list of regions, +// but removes any duplicate between the newly added region and any existing regions. +// +// In case the newly added region is fully contained in any pre-existing region, this function does nothing. +// Otherwise, any pre-existing regions that are fully contained in the newly added region are removed. +// +// This is done to avoid unnecessary reading pixels from GPU. +func appendRegionRemovingDuplicates(regions *[]image.Rectangle, region image.Rectangle) { + for _, r := range *regions { + if region.In(r) { + // The newly added rectangle is fully contained in one of the input regions. + // Nothing to add. + return } } - + // Separate loop, as regions must not get mutated before above return. n := 0 - for _, r := range regions { - if r.Empty() { + for _, r := range *regions { + if r.In(region) { continue } - regions[n] = r + (*regions)[n] = r n++ } - - return n + *regions = append((*regions)[:n], region) } diff --git a/internal/restorable/rect_test.go b/internal/restorable/rect_test.go index e7d20f933..27f094270 100644 --- a/internal/restorable/rect_test.go +++ b/internal/restorable/rect_test.go @@ -37,94 +37,129 @@ func areEqualRectangles(a, b []image.Rectangle) bool { func TestRemoveDuplicatedRegions(t *testing.T) { cases := []struct { - In []image.Rectangle - Out []image.Rectangle + Regions []image.Rectangle + NewRegions []image.Rectangle + Expected []image.Rectangle }{ { - In: nil, - Out: nil, + Regions: nil, + NewRegions: nil, + Expected: nil, }, { - In: []image.Rectangle{ + NewRegions: []image.Rectangle{ image.Rect(0, 0, 2, 2), }, - Out: []image.Rectangle{ + Expected: []image.Rectangle{ image.Rect(0, 0, 2, 2), }, }, { - In: []image.Rectangle{ + NewRegions: []image.Rectangle{ image.Rect(0, 0, 2, 2), image.Rect(0, 0, 1, 1), }, - Out: []image.Rectangle{ + Expected: []image.Rectangle{ image.Rect(0, 0, 2, 2), }, }, { - In: []image.Rectangle{ + NewRegions: []image.Rectangle{ image.Rect(0, 0, 1, 1), image.Rect(0, 0, 2, 2), }, - Out: []image.Rectangle{ + Expected: []image.Rectangle{ image.Rect(0, 0, 2, 2), }, }, { - In: []image.Rectangle{ + NewRegions: []image.Rectangle{ image.Rect(0, 0, 1, 3), image.Rect(0, 0, 2, 2), image.Rect(0, 0, 3, 1), }, - Out: []image.Rectangle{ + Expected: []image.Rectangle{ image.Rect(0, 0, 1, 3), image.Rect(0, 0, 2, 2), image.Rect(0, 0, 3, 1), }, }, { - In: []image.Rectangle{ + NewRegions: []image.Rectangle{ image.Rect(0, 0, 1, 3), image.Rect(0, 0, 2, 2), image.Rect(0, 0, 3, 1), image.Rect(0, 0, 4, 4), }, - Out: []image.Rectangle{ + Expected: []image.Rectangle{ image.Rect(0, 0, 4, 4), }, }, { - In: []image.Rectangle{ + NewRegions: []image.Rectangle{ image.Rect(0, 0, 1, 3), image.Rect(0, 0, 2, 2), image.Rect(0, 0, 3, 1), image.Rect(0, 0, 4, 4), image.Rect(1, 1, 2, 2), }, - Out: []image.Rectangle{ + Expected: []image.Rectangle{ image.Rect(0, 0, 4, 4), }, }, { - In: []image.Rectangle{ + NewRegions: []image.Rectangle{ image.Rect(0, 0, 1, 3), image.Rect(0, 0, 2, 2), image.Rect(0, 0, 3, 1), image.Rect(0, 0, 4, 4), image.Rect(0, 0, 5, 5), }, - Out: []image.Rectangle{ + Expected: []image.Rectangle{ + image.Rect(0, 0, 5, 5), + }, + }, + { + Regions: []image.Rectangle{ + image.Rect(0, 0, 1, 3), + image.Rect(0, 0, 2, 2), + image.Rect(0, 0, 3, 1), + image.Rect(0, 0, 4, 4), + }, + NewRegions: []image.Rectangle{ + image.Rect(0, 0, 5, 5), + }, + Expected: []image.Rectangle{ + image.Rect(0, 0, 5, 5), + }, + }, + { + Regions: []image.Rectangle{ + image.Rect(0, 0, 2, 2), + image.Rect(0, 0, 3, 1), + image.Rect(0, 0, 4, 4), + image.Rect(0, 0, 5, 5), + }, + NewRegions: []image.Rectangle{ + image.Rect(0, 0, 1, 3), + }, + Expected: []image.Rectangle{ + image.Rect(0, 0, 2, 2), + image.Rect(0, 0, 3, 1), + image.Rect(0, 0, 4, 4), image.Rect(0, 0, 5, 5), }, }, } for _, c := range cases { - n := restorable.RemoveDuplicatedRegions(c.In) - got := c.In[:n] - want := c.Out + got := c.Regions + for _, r := range c.NewRegions { + restorable.AppendRegionRemovingDuplicates(&got, r) + } + want := c.Expected if !areEqualRectangles(got, want) { - t.Errorf("restorable.RemoveDuplicatedRegions(%#v): got: %#v, want: %#v", c.In, got, want) + t.Errorf("restorable.RemoveDuplicatedRegions(%#v): got: %#v, want: %#v", c.NewRegions, got, want) } } }