How to optimize jpeg.Encode()

1,776 views
Skip to first unread message

Ming Li

unread,
Oct 13, 2015, 8:29:27 PM10/13/15
to golang-nuts
Hi there,

I am rewriting a simple Go program (from python) that encodes many JPEG pictures -- roughly 100 images to encode. I know encoding image is CPU intensive but the equivalent Python program, which using opencv c lib, takes much much less CPU cycles (maybe 1/10?). I then did a profiling the Go program. It's not surprising that most CPU cycles are used by jpeg.Encode. Please find the attached graph. Any suggestion that I can optimize it?


Thanks,
Ming

Benjamin Measures

unread,
Oct 14, 2015, 3:37:04 AM10/14/15
to golang-nuts
> the equivalent Python program, which using opencv c lib

You could try the equivalent Go program, using OpenCV C bindings: https://github.com/lazywei/go-opencv

Klaus Post

unread,
Oct 14, 2015, 5:56:31 AM10/14/15
to golang-nuts
On Wednesday, 14 October 2015 02:29:27 UTC+2, Ming Li wrote:
Hi there,

I am rewriting a simple Go program (from python) that encodes many JPEG pictures -- roughly 100 images to encode. I know encoding image is CPU intensive but the equivalent Python program, which using opencv c lib, takes much much less CPU cycles (maybe 1/10?). I then did a profiling the Go program. It's not surprising that most CPU cycles are used by jpeg.Encode. Please find the attached graph. Any suggestion that I can optimize it?

Wow - I didn't realize JPEG was this bad. All the conversions back and forth is horrible, both for quality and speed. The built in "image" is clearly made for ease of use and not performance.

6 of 18 seconds runtime is spent actually encoding the image, the rest is color space conversions. It seems that even if you supply YCbCr to the encoder, it is still converted YCbCr->RGB->YCbCr.

I would look outside the standard library. Speeding it up would require a bigger refactoring of the JPEG encoder. Also, all images are 4:2:0 subsampled, which means you will loose significant color quality, even if you use quality=100.

Thanks, 
Ming

 

Henrik Johansson

unread,
Oct 14, 2015, 7:30:07 AM10/14/15
to Klaus Post, golang-nuts
Hmmm... This is not my experience. I don't have any reproducible sample but decoding 100 images is hardly noticeably when I run a program doing a similar thing.
Of course the difference compared to a highly optimized C lib may be relatively large but for the human caller it is really fast.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Klaus Post

unread,
Oct 14, 2015, 7:47:39 AM10/14/15
to golang-nuts, klau...@gmail.com
On Wednesday, 14 October 2015 13:30:07 UTC+2, Henrik Johansson wrote:
Hmmm... This is not my experience. I don't have any reproducible sample but decoding 100 images is hardly noticeably when I run a program doing a similar thing.
Of course the difference compared to a highly optimized C lib may be relatively large but for the human caller it is really fast.

Decoding isn't nearly as bad, since it doesn't do any color space conversions, and you seem to get YCrCb un-converted. 

As a sidenote, there appears to be no chroma upsampling interpolation. I guess that is fast, and good for continuous YCrCb -> RGB -> YCrCb conversions, but bad for YCrCb -> RGB conversion, making red/blue colours blocky.

That said, a good *generic* image architecture is hard, but the JPEG encoder really shows the problems of the current.


/Klaus

Henrik Johansson

unread,
Oct 14, 2015, 8:04:12 AM10/14/15
to Klaus Post, golang-nuts

Actually, the program both encodes and decodes. If there is room for improvement perhaps it can be added. I kind find the api quite easy to use though so changes there would be nice to avoid.


--

Nigel Tao

unread,
Oct 14, 2015, 8:52:30 PM10/14/15
to Ming Li, golang-nuts
The image/jpeg encoder currently has fast-ish code paths if the image
being encoded is an *image.Gray or an *image.RGBA (see the writeSOS
method in image/jpeg/writer.go). If not, it will go through a much
slower path. The fact that your profile spends most of its time in
image/jpeg.toYCbCr (and image.(*YCbCr).At) suggests that you're going
through the slower path, and I'm guessing that your source images are
*image.YCbCr, as you would get if you're decoding JPEGs.

As medium term approaches, the image/jpeg library could add a fast-ish
code path for more image types. Specifically, encoding an *image.YCbCr
definitely should not need to round-trip through color.Color's RGBA
representation. The fast-ish code path could undoubtedly also be made
to go faster, if not significantly faster. There hasn't been much work
put into optimizing the encoder yet. There's definitely implementation
work that's worth doing, but I don't believe that the underlying
"image" and "image/etc" package designs are fundamentally flawed.

Unfortunately, I don't have much time to work on these medium term
suggestions myself, due to non-work-related reasons.

In the short term, you might see a noticable speed-up if you first
convert your images to *image.RGBA before encoding, which is
admittedly a lossy process, but JPEG is already lossy. In the long
term, once those optimizations above are done, this will end up being
slower than what you're doing now, but it might be faster right now.
See the "Converting an Image to RGBA" section of
http://blog.golang.org/go-imagedraw-package

As for encoding a subsample ratio other than 4:2:0, the jpeg.Options
struct gives us the ability to implement that if we wanted to. Again,
it's a matter of finding the time to do the work, and not necessarily
(in my biased opinion) that, modulo backwards compatibility, the APIs
require significant changes.

As for chroma interpolation when decoding, that is a classic time vs
quality trade-off. The standard library does the simpler, faster
choice of no interpolation, but the image.YCbCr type should have
enough information for someone to implement the more complicated,
slower but better looking version if they want to.
Reply all
Reply to author
Forward
0 new messages