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"
)
func RemoveDuplicatedRegions(regions []image.Rectangle) int {
return removeDuplicatedRegions(regions)
func AppendRegionRemovingDuplicates(regions *[]image.Rectangle, region image.Rectangle) {
appendRegionRemovingDuplicates(regions, region)
}

View File

@ -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)
}

View File

@ -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)
}
}
}