/ go Public

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

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

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

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

### 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 }``` The text was updated successfully, but these errors were encountered:
added this to the Unplanned milestone Jul 19, 2015

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

changed the title image/color: NRGBA(64).RGBA() optimisation image/color: NRGBA(64).RGBA() optimization Jul 20, 2015

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

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

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

 Sounds like a great place to start. :)

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

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