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