Image Loading Performance
Which image format is fastest to load? That was perhaps the most relevant question in last week’s article, so it’s time to explore it more deeply. Today’s article examines differences between different types of PNG, JPEG, and JPEG-XR files to answer questions like “does the JPEG quality setting matter?” and “is indexed PNG faster than full (ARGB) PNG?” Read on for the test and all the details.
To build today’s test, I started with the test code from last week’s article and modified it to remove the compression code and add more image types which I created using GIMP for PNG and BitmapData.encode
for JPEG and JPEG-XR. I also added reporting of the image file size and the rate at which it was decompressed (in bytes/sec). Here’s the code:
package { import flash.utils.getTimer; import flash.display.Bitmap; import flash.display.LoaderInfo; import flash.display.Loader; import flash.net.URLRequest; import flash.events.Event; import flash.net.URLLoaderDataFormat; import flash.net.URLLoader; import flash.display.BitmapData; import flash.utils.ByteArray; public class Test { private static const NUM_BITMAPS:int = 10; private var bmdBytes:ByteArray; private var bmds:Vector.<BitmapData> = new Vector.<BitmapData>(); private var url:String; private var label:String; private var callback:Function; public function Test(url:String, label:String) { this.url = url; this.label = label; } public function start(callback:Function): void { this.callback = callback; var loader:URLLoader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.BINARY; loader.addEventListener(Event.COMPLETE, onBytesLoaded); loader.load(new URLRequest(url)); } private function onBytesLoaded(ev:Event): void { bmdBytes = (ev.target as URLLoader).data; loadBitmap(); } private function loadBitmap(): void { var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded); loader.loadBytes(bmdBytes); } private function onLoaded(ev:Event): void { var bmd:BitmapData = ((ev.target as LoaderInfo).content as Bitmap).bitmapData; bmds.push(bmd); if (bmds.length < NUM_BITMAPS) { loadBitmap(); return; } var beforeTime:int; var afterTime:int; var i:int; var decompressTime:int; // Check decompression time beforeTime = getTimer(); for (i = 0; i < NUM_BITMAPS; ++i) { bmds[i].getPixel(0, 0); } afterTime = getTimer(); decompressTime = afterTime - beforeTime; callback(label, decompressTime, bmdBytes.length); } } }
package { import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize; public class DecompressPerformance extends Sprite { private static const FILENAME:String = "Adobe_Flash_Professional_CS5_icon"; private var logger:TextField; private var tests:Vector.<Test> = new <Test>[ new Test(FILENAME + ".png", "PNG (ARGB)"), new Test(FILENAME + "_grey.png", "PNG (greyscale)"), new Test(FILENAME + "_indexed.png", "PNG (indexed)"), new Test(FILENAME + "_q1.jpg", "JPEG (q=1)"), new Test(FILENAME + "_q50.jpg", "JPEG (q=50)"), new Test(FILENAME + "_q100.jpg", "JPEG (q=100)"), new Test(FILENAME + "_q1.jxr", "JPEG-XR (q=1)"), new Test(FILENAME + "_q50.jxr", "JPEG-XR (q=50)"), new Test(FILENAME + "_q100.jxr", "JPEG-XR (q=100)") ]; public function DecompressPerformance() { logger = new TextField(); logger.autoSize = TextFieldAutoSize.LEFT; logger.text = "Test,Decompress Time,File Size,Bytes/Sec\n"; addChild(logger); startNextTest(); } private function startNextTest(): void { if (tests.length == 0) { return; } tests.shift().start(onTestDone); } private function onTestDone(label:String, decompressTime:int, fileSize:uint): void { var rate:int = fileSize / (decompressTime/1000.0); logger.appendText(label + "," + decompressTime + "," + fileSize + "," + rate + "\n"); startNextTest(); } } }
I ran this test in the following environment:
- Flex SDK (MXMLC) 4.6.0.23201, compiling in release mode (no debugging or verbose stack traces)
- Release version of Flash Player 11.5.502.146
- 2.3 Ghz Intel Core i7
- Mac OS X 10.8.2
And here are the results I got:
Test | Decompress Time | File Size | Bytes/Sec |
---|---|---|---|
PNG (ARGB) | 499 | 68012 | 136296 |
PNG (greyscale) | 316 | 31386 | 99322 |
PNG (indexed) | 189 | 25782 | 136412 |
JPEG (q=1) | 143 | 18658 | 130475 |
JPEG (q=50) | 146 | 31188 | 213616 |
JPEG (q=100) | 167 | 126111 | 755155 |
JPEG-XR (q=1) | 873 | 228039 | 261213 |
JPEG-XR (q=50) | 644 | 10583 | 16433 |
JPEG-XR (q=100) | 632 | 4583 | 7251 |
Takeaways…
- Last week’s article called PNG 3x slower to load than JPEG. However, that’s only true for ARGB (full-color) PNG. Greyscale PNG is 40% quicker and indexed is takes less than half the time of ARGB PNG.
- The reason greyscale and indexed PNG are faster is due to their file size. Take a look at the decompression rates for PNG and you’ll see little difference between ARGB and Indexed.
- Increasing JPEG quality increases the load time slightly, but the rate of a 100% quality JPEG is 8x faster than a 1% quality JPEG. This would seem to indicate a huge fixed cost (overhead) to decompressing JPEG files.
- JPEG-XR is affected just like JPEG-XR, but the terminology is backward. Increasing the quality of a JPEG increases its file size and image quality. Increasing the quantization of a JPEG-XR decreases both of these.
- JPEG-XR speeds up a massive 22x between a quantization of 0 and a quantization of 50 but only about 2x between 50 and 100. Still, adding quality with JPEG-XR is a lot more costly than with JPEG.
As usual, you’ll have to decide for yourself how your assets should be compressed. Which format? Which compression options? The above results can only show you the load-time performance impact of your decision. You’ll need to balance that against image quality and file size, but it’s definitely worth considering the load-time performance since it can be quite costly: a 6x slowdown between JPEG at 1% quality and JPEG-XR at 1% quantization. Even on my relatively-fast quad core CPU that’s a 70 millisecond difference, 4 whole frames at 60 FPS. How long would it be on a slower CPU like a mobile device?
Spot a bug? Got a question, comment, or suggestion? Post a comment!
#1 by henke37 on February 11th, 2013 ·
Also, load time is not just decompression, but also the network io part.
#2 by Etherlord on February 11th, 2013 ·
I think if you consider network, the time of decompressing doesn’t matter. This is more important when loading assets from hard drive.
#3 by jackson on February 11th, 2013 ·
You’ll definitely need to consider I/O (network, hard drive, flash drive, etc.) and that’s where the “file size” results in the article come in. However, this article is about the forgotten aspect of load times: decompressing the images and the CPU hit you take. With more and more AS3 being written for AIR on mobile devices rather than the Flash plugin for web browsers, I/O has gotten way faster when loading images from flash memory and CPU has gotten way slower with ARM chips. So I think it’s an opportune time to consider CPU in addition to just I/O.