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
This commit is contained in:
divVerent 2023-04-08 10:31:22 -07:00 committed by GitHub
parent ce71c31a27
commit cc24796270
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 91 additions and 68 deletions

View File

@ -18,6 +18,6 @@ import (
"image" "image"
) )
func RemoveDuplicatedRegions(regions []image.Rectangle) int { func AppendRegionRemovingDuplicates(regions *[]image.Rectangle, region image.Rectangle) {
return removeDuplicatedRegions(regions) appendRegionRemovingDuplicates(regions, region)
} }

View File

@ -240,26 +240,26 @@ func (i *Image) makeStale(rect image.Rectangle) {
return return
} }
origNum := len(i.staleRegions) var addedRegions []image.Rectangle
i.staleRegions = i.appendRegionsForDrawTriangles(i.staleRegions) i.appendRegionsForDrawTriangles(&addedRegions)
if !rect.Empty() { if !rect.Empty() {
i.staleRegions = append(i.staleRegions, rect) appendRegionRemovingDuplicates(&addedRegions, rect)
}
for _, rect := range addedRegions {
appendRegionRemovingDuplicates(&i.staleRegions, rect)
} }
i.clearDrawTrianglesHistory() i.clearDrawTrianglesHistory()
// Clear pixels to save memory. // Clear pixels to save memory.
for _, r := range i.staleRegions[origNum:] { for _, r := range addedRegions {
if r.Empty() { if r.Empty() {
continue continue
} }
i.basePixels.Clear(r.Min.X, r.Min.Y, r.Dx(), r.Dy()) 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. // Don't have to call makeStale recursively here.
// Restoring is done after topological sorting is done. // Restoring is done after topological sorting is done.
// If an image depends on another stale image, this means that // 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 { if i.stale {
rs = i.staleRegions rs = i.staleRegions
} else { } else {
i.regionsCache = i.appendRegionsForDrawTriangles(i.regionsCache) i.appendRegionsForDrawTriangles(&i.regionsCache)
defer func() { defer func() {
i.regionsCache = i.regionsCache[:0] i.regionsCache = i.regionsCache[:0]
}() }()
n := removeDuplicatedRegions(i.regionsCache) rs = i.regionsCache
rs = i.regionsCache[:n]
} }
for _, r := range rs { 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. // In order to clear the draw-triangles history, read pixels from GPU.
if len(i.drawTrianglesHistory) > 0 { if len(i.drawTrianglesHistory) > 0 {
i.regionsCache = i.appendRegionsForDrawTriangles(i.regionsCache) i.appendRegionsForDrawTriangles(&i.regionsCache)
defer func() { defer func() {
i.regionsCache = i.regionsCache[:0] 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() { if r.Empty() {
continue continue
} }
@ -698,18 +695,14 @@ func (i *Image) InternalSize() (int, int) {
return i.image.InternalSize() return i.image.InternalSize()
} }
func (i *Image) appendRegionsForDrawTriangles(regions []image.Rectangle) []image.Rectangle { func (i *Image) appendRegionsForDrawTriangles(regions *[]image.Rectangle) {
n := len(regions)
for _, d := range i.drawTrianglesHistory { for _, d := range i.drawTrianglesHistory {
r := regionToRectangle(d.dstRegion) r := regionToRectangle(d.dstRegion)
if r.Empty() { if r.Empty() {
continue continue
} }
regions = append(regions, r) appendRegionRemovingDuplicates(regions, r)
} }
nn := removeDuplicatedRegions(regions[n:])
return regions[:n+nn]
} }
func regionToRectangle(region graphicsdriver.Region) image.Rectangle { 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)))) int(math.Ceil(float64(region.Y+region.Height))))
} }
// removeDuplicatedRegions removes duplicated regions and returns the new size of the slice. // appendRegionRemovingDuplicates adds a region to a given list of regions,
// If a region covers other regions, the covered regions are removed. // but removes any duplicate between the newly added region and any existing regions.
func removeDuplicatedRegions(regions []image.Rectangle) int { //
for i, r := range regions { // In case the newly added region is fully contained in any pre-existing region, this function does nothing.
if r.Empty() { // Otherwise, any pre-existing regions that are fully contained in the newly added region are removed.
continue //
} // This is done to avoid unnecessary reading pixels from GPU.
for j, rr := range regions { func appendRegionRemovingDuplicates(regions *[]image.Rectangle, region image.Rectangle) {
if i == j { for _, r := range *regions {
continue if region.In(r) {
} // The newly added rectangle is fully contained in one of the input regions.
if rr.Empty() { // Nothing to add.
continue return
}
if rr.In(r) {
regions[j] = image.Rectangle{}
} }
} }
} // Separate loop, as regions must not get mutated before above return.
n := 0 n := 0
for _, r := range regions { for _, r := range *regions {
if r.Empty() { if r.In(region) {
continue continue
} }
regions[n] = r (*regions)[n] = r
n++ n++
} }
*regions = append((*regions)[:n], region)
return n
} }

View File

@ -37,94 +37,129 @@ func areEqualRectangles(a, b []image.Rectangle) bool {
func TestRemoveDuplicatedRegions(t *testing.T) { func TestRemoveDuplicatedRegions(t *testing.T) {
cases := []struct { cases := []struct {
In []image.Rectangle Regions []image.Rectangle
Out []image.Rectangle NewRegions []image.Rectangle
Expected []image.Rectangle
}{ }{
{ {
In: nil, Regions: nil,
Out: nil, NewRegions: nil,
Expected: nil,
}, },
{ {
In: []image.Rectangle{ NewRegions: []image.Rectangle{
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
}, },
Out: []image.Rectangle{ Expected: []image.Rectangle{
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
}, },
}, },
{ {
In: []image.Rectangle{ NewRegions: []image.Rectangle{
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
image.Rect(0, 0, 1, 1), image.Rect(0, 0, 1, 1),
}, },
Out: []image.Rectangle{ Expected: []image.Rectangle{
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
}, },
}, },
{ {
In: []image.Rectangle{ NewRegions: []image.Rectangle{
image.Rect(0, 0, 1, 1), image.Rect(0, 0, 1, 1),
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
}, },
Out: []image.Rectangle{ Expected: []image.Rectangle{
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
}, },
}, },
{ {
In: []image.Rectangle{ NewRegions: []image.Rectangle{
image.Rect(0, 0, 1, 3), image.Rect(0, 0, 1, 3),
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
image.Rect(0, 0, 3, 1), image.Rect(0, 0, 3, 1),
}, },
Out: []image.Rectangle{ Expected: []image.Rectangle{
image.Rect(0, 0, 1, 3), image.Rect(0, 0, 1, 3),
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
image.Rect(0, 0, 3, 1), image.Rect(0, 0, 3, 1),
}, },
}, },
{ {
In: []image.Rectangle{ NewRegions: []image.Rectangle{
image.Rect(0, 0, 1, 3), image.Rect(0, 0, 1, 3),
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
image.Rect(0, 0, 3, 1), image.Rect(0, 0, 3, 1),
image.Rect(0, 0, 4, 4), image.Rect(0, 0, 4, 4),
}, },
Out: []image.Rectangle{ Expected: []image.Rectangle{
image.Rect(0, 0, 4, 4), image.Rect(0, 0, 4, 4),
}, },
}, },
{ {
In: []image.Rectangle{ NewRegions: []image.Rectangle{
image.Rect(0, 0, 1, 3), image.Rect(0, 0, 1, 3),
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
image.Rect(0, 0, 3, 1), image.Rect(0, 0, 3, 1),
image.Rect(0, 0, 4, 4), image.Rect(0, 0, 4, 4),
image.Rect(1, 1, 2, 2), image.Rect(1, 1, 2, 2),
}, },
Out: []image.Rectangle{ Expected: []image.Rectangle{
image.Rect(0, 0, 4, 4), image.Rect(0, 0, 4, 4),
}, },
}, },
{ {
In: []image.Rectangle{ NewRegions: []image.Rectangle{
image.Rect(0, 0, 1, 3), image.Rect(0, 0, 1, 3),
image.Rect(0, 0, 2, 2), image.Rect(0, 0, 2, 2),
image.Rect(0, 0, 3, 1), image.Rect(0, 0, 3, 1),
image.Rect(0, 0, 4, 4), image.Rect(0, 0, 4, 4),
image.Rect(0, 0, 5, 5), 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), image.Rect(0, 0, 5, 5),
}, },
}, },
} }
for _, c := range cases { for _, c := range cases {
n := restorable.RemoveDuplicatedRegions(c.In) got := c.Regions
got := c.In[:n] for _, r := range c.NewRegions {
want := c.Out restorable.AppendRegionRemovingDuplicates(&got, r)
}
want := c.Expected
if !areEqualRectangles(got, want) { 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)
} }
} }
} }