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
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Milestone

Comments

@pierrre
Copy link

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

CC @nigeltao

@nigeltao
Copy link
Contributor

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 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 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

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 commented Jul 21, 2015

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

@nigeltao
Copy link
Contributor

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 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 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

Sounds like a great place to start. :)

@pierrre
Copy link
Author

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

@dmitshur dmitshur added Performance NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Aug 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Projects
None yet
Development

No branches or pull requests

5 participants