JDK-8100247 : Remove inefficiencies from ImageLoader
  • Type: Sub-task
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: fx1.0
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2008-11-22
  • Updated: 2015-06-16
  • Resolved: 2008-12-03
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
fx1.1Fixed
Description
Profiling has uncovered some inefficient use of Image I/O by the ImageLoader code (which is the implementation of javafx.scene.image.Image).  This is low hanging fruit.  More details to come...


Comments
I'm attaching a pure Java testcase that demonstrates the performance gain from doing in-place ColorSpace conversion after the fact (instead of letting JPEGImageReader do it one scanline at a time). Here's what it prints out (loading gorilla.jpg) on my 2.4GHz Core2Duo MBP with JDK 6u10: ColorSpace conversion done by JPEGImageReader... total: 1078 total: 1000 total: 1015 ColorSpace conversion done in-place by ColorConvertOp... total: 782 total: 781 total: 812 About a 20% improvement overall...
02-12-2008

To recap, here is a summary of the performance gains after applying each fix in succession on JDK 6u10 on Windows XP on my 2.4 GHz MacBook Pro (time in milliseconds for loading gorilla.jpg after a few warmup runs)... Loading at original size (no scaling): baseline: 1313 ms avoid getNumImages(): 1125 ms use MemoryCacheImageInputStream: 1047 ms optimized colorspace conversion: 843 ms Loading as thumbnail (width=100, preserveRatio=true): baseline: 1420 ms avoid getNumImages(): 1188 ms use MemoryCacheImageInputStream: 1120 ms optimized colorspace conversion: 906 ms
02-12-2008

One more thing... In JDK 5, the JPEGImageReader would by default return a BufferedImage with an embedded ColorSpace (if the original JPEG file contained a color profile), which added extra time to the decoding process, and would actually make image rendering slower as well since we'd be doing ColorSpace conversion at render time. This was fixed in JDK 6 to (by default) return a BufferedImage with default RGB ColorSpace; see: http://bugs.sun.com/view_bug.do?bug_id=4705399 At some point we will need an option on Image that allows the developer to control whether color matching is enabled. Until then, we should put a workaround in ImageLoader similar to mimic what was done in 4705399. This gives us another boost, mainly at scaling time: TESTING: file:/Users/campbell/Projects/Sun/tmp/gorilla.jpg create stream: 0 find reader: 1 get num images: 0 read: 1312 scale: 101 close stream: 0 remove listeners: 0 dispose reader: 0 readImage: 1414 total: 1415ms TESTING: file:/Users/campbell/Projects/Sun/tmp/flower2.jpg create stream: 0 find reader: 1 get num images: 0 read: 1213 scale: 101 close stream: 1 remove listeners: 0 dispose reader: 0 readImage: 1316 total: 1316ms
23-11-2008

So why is closing the stream so costly? By default, ImageIO.createImageInputStream() will return a FileCacheImageInputStream, which is backed by a RandomAccessFile. Creating and disposing the cache file can be expensive, as we're seeing here. Most times we recommend disabling the use of file caching via ImageIO.setUseCache(false). With this simple tweak in place, we avoid the slow stream close: TESTING: file:/Users/campbell/Projects/Sun/tmp/gorilla.jpg create stream: 0 find reader: 1 get num images: 0 read: 1279 scale: 236 close stream: 0 remove listeners: 0 dispose reader: 0 readImage: 1516 total: 1516ms TESTING: file:/Users/campbell/Projects/Sun/tmp/flower2.jpg create stream: 1 find reader: 0 get num images: 0 read: 1194 scale: 234 close stream: 1 remove listeners: 0 dispose reader: 0 readImage: 1431 total: 1431ms More to follow...
23-11-2008

And here is some quick-and-dirty profiling information after instrumenting the ImageLoader.readImageFromStream() method: TESTING: file:/Users/campbell/Projects/Sun/tmp/gorilla.jpg create stream: 0 find reader: 1 get num images: 642 read: 1272 scale: 234 close stream: 182 remove listeners: 0 dispose reader: 0 readImage: 2332 total: 2332ms TESTING: file:/Users/campbell/Projects/Sun/tmp/flower2.jpg create stream: 0 find reader: 1 get num images: 155 read: 1194 scale: 235 close stream: 158 remove listeners: 0 dispose reader: 0 readImage: 1743 total: 1744ms As you can see, we spend lots of time calling reader.getNumImages(), even though this information is not useful for formats other than animated GIF. We can easily reorder the code to avoid calling this altogether. Now it looks like: TESTING: file:/Users/campbell/Projects/Sun/tmp/gorilla.jpg create stream: 0 find reader: 1 get num images: 0 read: 1610 scale: 235 close stream: 163 remove listeners: 0 dispose reader: 0 readImage: 2010 total: 2011ms TESTING: file:/Users/campbell/Projects/Sun/tmp/flower2.jpg create stream: 1 find reader: 1 get num images: 0 read: 1253 scale: 236 close stream: 190 remove listeners: 0 dispose reader: 0 readImage: 1682 total: 1682ms More to follow...
23-11-2008

I'm attaching a quick-and-dirty FX testcase (ImagePerf.fx) for measuring performance of thumbnail creation. There are two images included: - gorilla.jpg (direct from Canon EOS Digital Rebel XTi camera in JPEG mode) - flower2.jpg (taken with same camera in RAW mode, converted to JPEG in Lightroom 2)
23-11-2008