Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

image/color: NRGBA(64).RGBA() optimization #11793

Open
pierrre opened this issue Jul 19, 2015 · 11 comments
Open

image/color: NRGBA(64).RGBA() optimization #11793

pierrre opened this issue Jul 19, 2015 · 11 comments

Comments

@pierrre
Copy link

@pierrre pierrre commented Jul 19, 2015

If alpha is equal to 0xffff, return r,b,g,a.
If alpha is equal to 0, return 0,0,0,0.
Else, multiply by alpha, divide by 0xffff, return r,g,b,a.

New code:

func (c NRGBA) RGBA() (r, g, b, a uint32) {
    a = uint32(c.A)
    a |= a << 8
    if a == 0 {
        return 0, 0, 0, 0
    }
    r = uint32(c.R)
    r |= r << 8
    g = uint32(c.G)
    g |= g << 8
    b = uint32(c.B)
    b |= b << 8
    if a == 0xffff {
        return
    }
    r = r * a / 0xffff
    g = g * a / 0xffff
    b = b * a / 0xffff
    return
}

func (c NRGBA64) RGBA() (r, g, b, a uint32) {
    a = uint32(c.A)
    if a == 0 {
        return 0, 0, 0, 0
    }
    r = uint32(c.R)
    g = uint32(c.G)
    b = uint32(c.B)
    if a == 0xffff {
        return
    }
    r = r * a / 0xffff
    g = g * a / 0xffff
    b = b * a / 0xffff
    return
}
@ianlancetaylor ianlancetaylor added this to the Unplanned milestone Jul 19, 2015
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 19, 2015

@nigeltao
Copy link
Contributor

@nigeltao nigeltao commented Jul 20, 2015

It might be faster, or it might not: branches may or may not be faster than branch-free-but-redundant arithmetic. Actual data would be useful.

@pierrre
Copy link
Author

@pierrre pierrre commented Jul 20, 2015

My benchmark:

package main

import (
    "image/color"
    "testing"
)

func BenchmarkNRGBAToRGBAOpaque(b *testing.B) {
    c := color.NRGBA{0xff, 0x80, 0x00, 0xff}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBAToRGBATransparent(b *testing.B) {
    c := color.NRGBA{0xff, 0x80, 0x00, 0x00}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBAToRGBATranslucent(b *testing.B) {
    c := color.NRGBA{0xff, 0x80, 0x00, 0x80}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBA64ToRGBAOpaque(b *testing.B) {
    c := color.NRGBA64{0xffff, 0x8000, 0x0000, 0xffff}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBA64ToRGBATransparent(b *testing.B) {
    c := color.NRGBA64{0xffff, 0x8000, 0x0000, 0x0000}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBA64ToRGBATranslucent(b *testing.B) {
    c := color.NRGBA64{0xffff, 0x8000, 0x0000, 0x8000}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

Result:

benchmark                               old ns/op     new ns/op     delta
BenchmarkNRGBAToRGBAOpaque-8            6.02          2.62          -56.48%
BenchmarkNRGBAToRGBATransparent-8       6.12          1.10          -82.03%
BenchmarkNRGBAToRGBATranslucent-8       6.07          6.38          +5.11%
BenchmarkNRGBA64ToRGBAOpaque-8          4.89          1.78          -63.60%
BenchmarkNRGBA64ToRGBATransparent-8     4.80          1.08          -77.50%
BenchmarkNRGBA64ToRGBATranslucent-8     4.80          5.06          +5.42%

Go version: Go 1.5 beta2
CPU: Intel(R) Core(TM) i7-3610QM CPU @ 2.30GHz

@pierrre pierrre changed the title image/color: NRGBA(64).RGBA() optimisation image/color: NRGBA(64).RGBA() optimization Jul 20, 2015
@pierrre
Copy link
Author

@pierrre pierrre commented Jul 20, 2015

Just updated my benchmark with NRGBA & NRGBA64 result.
The new code is a little bit slower for semi transparent pixels...
I think most images consist of opaque or transparent pixels, very few semi transparent.

@nigeltao
Copy link
Contributor

@nigeltao nigeltao commented Jul 21, 2015

Most pixels are probably fully opaque or fully transparent.

Still, I might be wrong here, but I would have thought most of the calls to color.NRGBA's RGBA method would be indirect, through the color.Color interface, and once you pull in the overhead of a per-pixel interface method call, the percentage delta will be a lot less dramatic. For example, using the standard library's image/draw package will go through this interface.

CC'ing @robpike if he has thoughts on whether this micro-optimization is worth it.

@robpike
Copy link
Contributor

@robpike robpike commented Jul 21, 2015

I believe these two cases are dynamically dominant, so why not?

@nigeltao
Copy link
Contributor

@nigeltao nigeltao commented Jul 21, 2015

OK, I'll make a patch (or take a patch, if you want to cook one up), but please wait until the tree opens after Go 1.5 is released.

@pierrre
Copy link
Author

@pierrre pierrre commented Jul 21, 2015

In my benchmark, if I initialize the color with

c := color.Color(color.NRGBA64{0xffff, 0x8000, 0x0000, 0xffff})

I get this:

benchmark                               old ns/op     new ns/op     delta
BenchmarkNRGBAToRGBAOpaque-8            12.8          8.76          -31.56%
BenchmarkNRGBAToRGBATransparent-8       12.8          6.10          -52.34%
BenchmarkNRGBAToRGBATranslucent-8       12.9          12.6          -2.33%
BenchmarkNRGBA64ToRGBAOpaque-8          11.2          6.68          -40.36%
BenchmarkNRGBA64ToRGBATransparent-8     11.4          5.63          -50.61%
BenchmarkNRGBA64ToRGBATranslucent-8     11.4          10.3          -9.65%

(I'm running this benchmark on a different computer: Virtualbox, Linux, Go 1.5 beta2, Intel(R) Core(TM) i7 CPU 930 @ 2.80GHz)

I agree, it's a micro-optimization.

@pierrre
Copy link
Author

@pierrre pierrre commented Jul 21, 2015

but please wait until the tree opens after Go 1.5 is released.

I agree, it's not high priority.

(or take a patch, if you want to cook one up)

This could be my first (micro) contribution to Go :)

@nigeltao
Copy link
Contributor

@nigeltao nigeltao commented Jul 22, 2015

Sounds like a great place to start. :)

@pierrre
Copy link
Author

@pierrre pierrre commented Jul 23, 2015

New benchmark: added image NRGBA to JPEG (https://raw.githubusercontent.com/pierrre/imageserver/master/testdata/medium.jpg)
TL;DR: -3~4% 😒

go test -x -a -bench=. -benchmem -benchtime=10s

benchcmp

benchmark                               old ns/op     new ns/op     delta
BenchmarkNRGBAToRGBAOpaque-8            6.02          2.56          -57.48%
BenchmarkNRGBAToRGBATransparent-8       6.01          1.05          -82.53%
BenchmarkNRGBAToRGBATranslucent-8       6.01          6.27          +4.33%
BenchmarkNRGBA64ToRGBAOpaque-8          4.78          1.77          -62.97%
BenchmarkNRGBA64ToRGBATransparent-8     4.78          1.05          -78.03%
BenchmarkNRGBA64ToRGBATranslucent-8     4.78          4.90          +2.51%
BenchmarkImageNRGBAToJPEG-8             130436983     124108320     -4.85%
BenchmarkImageNRGBA64ToJPEG-8           167729682     162254647     -3.26%

old

testing: warning: no tests to run
PASS
BenchmarkNRGBAToRGBAOpaque-8        2000000000           6.02 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBAToRGBATransparent-8   3000000000           6.01 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBAToRGBATranslucent-8   2000000000           6.01 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBA64ToRGBAOpaque-8      3000000000           4.78 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBA64ToRGBATransparent-8 3000000000           4.78 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBA64ToRGBATranslucent-8 3000000000           4.78 ns/op        0 B/op          0 allocs/op
BenchmarkImageNRGBAToJPEG-8              100     130436983 ns/op    13635888 B/op     851972 allocs/op
BenchmarkImageNRGBA64ToJPEG-8            100     167729682 ns/op    13635888 B/op     851972 allocs/op
ok      _test/benchrgba 119.014s

new

testing: warning: no tests to run
PASS
BenchmarkNRGBAToRGBAOpaque-8        10000000000          2.56 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBAToRGBATransparent-8   10000000000          1.05 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBAToRGBATranslucent-8   2000000000           6.27 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBA64ToRGBAOpaque-8      10000000000          1.77 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBA64ToRGBATransparent-8 10000000000          1.05 ns/op        0 B/op          0 allocs/op
BenchmarkNRGBA64ToRGBATranslucent-8 3000000000           4.90 ns/op        0 B/op          0 allocs/op
BenchmarkImageNRGBAToJPEG-8              100     124108320 ns/op    13635888 B/op     851972 allocs/op
BenchmarkImageNRGBA64ToJPEG-8            100     162254647 ns/op    13635888 B/op     851972 allocs/op
ok      _test/benchrgba 122.781s

code

package main

import (
    "image"
    "image/color"
    "image/draw"
    "image/jpeg"
    _ "image/jpeg"
    _ "image/png"
    "io/ioutil"
    "os"
    "testing"
)

func BenchmarkNRGBAToRGBAOpaque(b *testing.B) {
    c := color.NRGBA{0xff, 0x80, 0x00, 0xff}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBAToRGBATransparent(b *testing.B) {
    c := color.NRGBA{0xff, 0x80, 0x00, 0x00}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBAToRGBATranslucent(b *testing.B) {
    c := color.NRGBA{0xff, 0x80, 0x00, 0x80}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBA64ToRGBAOpaque(b *testing.B) {
    c := color.NRGBA64{0xffff, 0x8000, 0x0000, 0xffff}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBA64ToRGBATransparent(b *testing.B) {
    c := color.NRGBA64{0xffff, 0x8000, 0x0000, 0x0000}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

func BenchmarkNRGBA64ToRGBATranslucent(b *testing.B) {
    c := color.NRGBA64{0xffff, 0x8000, 0x0000, 0x8000}
    for i := 0; i < b.N; i++ {
        c.RGBA()
    }
}

var (
    imNRGBA   *image.NRGBA
    imNRGBA64 *image.NRGBA64
)

func init() {
    f, err := os.Open("/home/pierre/Go/src/github.com/pierrre/imageserver/testdata/medium.jpg")
    if err != nil {
        panic(err)
    }
    im, _, err := image.Decode(f)
    if err != nil {
        panic(err)
    }
    imNRGBA = image.NewNRGBA(image.Rect(0, 0, im.Bounds().Dx(), im.Bounds().Dy()))
    draw.Draw(imNRGBA, imNRGBA.Bounds(), im, im.Bounds().Min, draw.Src)
    imNRGBA64 = image.NewNRGBA64(image.Rect(0, 0, im.Bounds().Dx(), im.Bounds().Dy()))
    draw.Draw(imNRGBA64, imNRGBA64.Bounds(), im, im.Bounds().Min, draw.Src)
}

func BenchmarkImageNRGBAToJPEG(b *testing.B) {
    for i := 0; i < b.N; i++ {
        err := jpeg.Encode(ioutil.Discard, imNRGBA, nil)
        if err != nil {
            b.Fatal(err)
        }
    }
}

func BenchmarkImageNRGBA64ToJPEG(b *testing.B) {
    for i := 0; i < b.N; i++ {
        err := jpeg.Encode(ioutil.Discard, imNRGBA64, nil)
        if err != nil {
            b.Fatal(err)
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
You can’t perform that action at this time.