Skip to content

image/draw: increase performances by applying special case if mask is *image.Alpha #46395

Closed
@owulveryck

Description

@owulveryck

What version of Go are you using (go version)?

$ go version
go version devel go1.17-e4615ad74d Wed May 26 13:25:43 2021 +0000 darwin/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE="off"
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/olivierwulveryck/Library/Caches/go-build"
GOENV="/Users/olivierwulveryck/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/olivierwulveryck/GOPROJECTS/pkg/mod"
GONOPROXY="github.com/dktunited"
GONOSUMDB="github.com/dktunited"
GOOS="darwin"
GOPATH="/Users/olivierwulveryck/GOPROJECTS"
GOPRIVATE="github.com/dktunited"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/Users/olivierwulveryck/dev/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/Users/olivierwulveryck/dev/go/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="devel go1.17-e4615ad74d Wed May 26 13:25:43 2021 +0000"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/j3/t2_lsqzd5mgbv7tqvxg9ty5r0000gn/T/go-build4289980978=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

I am building a tool that is manipulating a lot of images. The process is a bit slow.
I made some performance analysis, and it appears that the drawRGBA method is really time-consuming.
Specially the call to mask.At(mx,my).RGBA() is causing performance penalty.

I think that most of the use cases of DrawMask function are using an *image.Alpha structure as a mask. Therefore doing a special treatment if the mask is *image.Alpha could be valuable for most of the usage.

I made a simple test and bench for the drawRGBA method (it should be done for other methods as well).

func Benchmark_drawRGBA(b *testing.B) {
	r := image.Rect(0, 0, 500, 500)
	dst := image.NewRGBA(r)
	mask := image.NewAlpha(r)
	src := image.NewRGBA(r)
	for i := 0; i < b.N; i++ {
		drawRGBA(dst, r, src, image.Point{}, mask, image.Point{}, Over)
	}
}

Then I made a patch as an experiment:

diff --git a/src/image/draw/draw.go b/src/image/draw/draw.go
index 8f96aa2d18..205e8cfb43 100644
--- a/src/image/draw/draw.go
+++ b/src/image/draw/draw.go
@@ -530,7 +530,13 @@ func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Poin
                for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
                        ma := uint32(m)
                        if mask != nil {
-                               _, _, _, ma = mask.At(mx, my).RGBA()
+                               if mask, ok := mask.(*image.Alpha); ok {
+                                       off := mask.PixOffset(mx, my)
+                                       ma = uint32(mask.Pix[off])
+                                       ma |= ma << 8
+                               } else {
+                                       _, _, _, ma = mask.At(mx, my).RGBA()
+                               }
                        }
                        sr, sg, sb, sa := src.At(sx, sy).RGBA()
                        d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857

The performance enhancement is not negligible:

benchmark                 old ns/op     new ns/op     delta
Benchmark_drawRGBA-12     13178981      8599585       -34.75%

benchmark                 old allocs     new allocs     delta
Benchmark_drawRGBA-12     500000         250000         -50.00%

benchmark                 old bytes     new bytes     delta
Benchmark_drawRGBA-12     2025807       1016234       -49.84%

This is a test for opening the discussion, and I guess that other controls should be added as it makes some tests of the draw package fail. Specifically, the test.mask causes panic, and I cannot figure out what test.mask is nor where it is defined.

What did you expect to see?

N/A

What did you see instead?

N/A

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Performance

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions