Today’s article is in response to a comment left about my article on try/catch slowdowns. The second time around I will provide an example that is hopefully more “real world” than the last article provided.

The comment claimed:

Typical real world applications will not halt the LLVM for 100000000 iterations; therefore the slowness will negligible. Hopefully 10.1 will address this.

I have to concede that summing the first 100000000 positive integers is indeed an unrealistic test and the criticism is therefore valid. This piqued my curiosity. Perhaps when I claimed a 2-3x slowdown I had vastly overstated the effect of a try/catch block on performance and that is may be, as suggested, negligible. The difficulty though is in devising a test that both mimics a “real world” application and is simple enough to post on a blog. I cannot hope that readers will come to a full understanding of the computational tasks a full application undertakes, so I feel compelled to provide something more simple. This led me to post the simple loop summing up all those integers.

In today’s example, I have chosen a task that is both expensive on its own and realistic enough that it may well be undertaken by an application in the “real world”. Like the last example, it too includes many loop iterations, but this time to a more realistic point. I have chosen to set every pixel on a large, but not unreasonably large, BitmapData. Such a task is undertaken by image encoders, bicubic image resizers, and ray tracers, to name a couple of examples. My example won’t do anything interesting with the image, just iterate over it making a funky pattern and then showing that pattern. This, I hope, is a true “real world” task:

package
{
	import flash.display.*;
	import flash.events.*;
	import flash.text.*;
	import flash.geom.*;
	import flash.ui.*;
	import flash.utils.*;
 
	[SWF(backgroundColor="0xEEEADB",frameRate="40",width="640",height="480")]
 
	/**
	*   A (hopefully) real world test of try/catch performance
	*   @author Jackson Dunstan
	*/
	public class TryCatchTester extends Sprite
	{
		/** If we should use a try/catch block during update */
		private var __useTryCatch:Boolean;
 
 		/** Last time we updated FPS */
		private var __lastFPSUpdateTime:uint;
 
		/** Last time we entered a frame */
		private var __lastFrameTime:uint;
 
		/** Count of frames since the last time we updated the FPS */
		private var __frameCount:uint;
 
		/** Framerate display */
		private var __framerate:TextField;
 
		/** Text field to show status */
		private var __status:TextField;
 
		/** Display of the results */
		private var __display:Bitmap;
 
		/** A bitmap to compose the image on before drawing it to the display */
		private var __backBuffer:BitmapData;
 
		/**
		*   Application entry point
		*/
		public function TryCatchTester()
		{
			// Setup stage
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
 			// Setup the display
 			var bmd:BitmapData = new BitmapData(2000, 2000, false, 0x00000000);
 			__display = new Bitmap(bmd);
 			addChild(__display);
 			__backBuffer = new BitmapData(2000, 2000, false, 0x00000000);
 
 			var format:TextFormat = new TextFormat();
			format.font = "_sans";
 
			// Setup framerate display
			__framerate = new TextField();
			__framerate.autoSize = TextFieldAutoSize.LEFT;
			__framerate.background = true;
			__framerate.backgroundColor = 0xEEEADB;
			__framerate.selectable = false;
			__framerate.text = "Gathering FPS data...";
			__framerate.setTextFormat(format);
			__framerate.defaultTextFormat = format;
			addChild(__framerate);
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
 
			// Setup logger
			__status = new TextField();
			__status.y = __framerate.height;
			__status.autoSize = TextFieldAutoSize.LEFT;
			__status.background = true;
			__status.backgroundColor = 0xEEEADB;
			__status.selectable = false;
			__status.text = "Use try/catch block: " + __useTryCatch;
			__status.setTextFormat(format);
			__status.defaultTextFormat = format;
			addChild(__status);
 
 			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
		}
 
		/**
		*   Callback for when a frame is entered
		*   @param ev ENTER_FRAME event
		*/
		private function onEnterFrame(ev:Event): void
		{
			// Update frame rate counter
			__frameCount++;
			var now:int = getTimer();
			var dTime:int = now - __lastFrameTime;
			var elapsed:int = now - __lastFPSUpdateTime;
			if (elapsed > 1000)
			{
				var framerateValue:Number = 1000 / (elapsed / __frameCount);
				if (__framerate.visible)
				{
					__framerate.text = "FPS: " + framerateValue.toFixed(4);
				}
				__lastFPSUpdateTime = now;
				__frameCount = 0;
			}
			__lastFrameTime = now;
 
			// Do work, optionally in a try/catch block
			if (__useTryCatch)
			{
				updateTryCatch(dTime);
			}
			else
			{
				updateNoTryCatch(dTime);
			}
		}
 
		/**
		*   Update based on elapsed time with NO try/catch
		*   @param elapsed Number of milliseconds elapsed since the last update
		*/
		private function updateNoTryCatch(elapsed:int): void
		{
			var bmd:BitmapData = __backBuffer;
			for (var x:int = 0; x < 2000; ++x)
			{
				for (var y:int = 0; y < 2000; ++y)
				{
					bmd.setPixel32(x, y, x*y);
				}
			}
			__display.bitmapData.draw(bmd);
		}
 
		/**
		*   Update based on elapsed time WITH a try/catch
		*   @param elapsed Number of milliseconds elapsed since the last update
		*/
		private function updateTryCatch(elapsed:int): void
		{
			try
			{
				var bmd:BitmapData = __backBuffer;
				for (var x:int = 0; x < 2000; ++x)
				{
					for (var y:int = 0; y < 2000; ++y)
					{
						bmd.setPixel32(x, y, x*y);
					}
				}
				__display.bitmapData.draw(bmd);
			}
			catch (err:Error)
			{
			}
		}
 
		/**
		*   Callback for when a key is pressed anywhere on the stage
		*   @param ev KEY_DOWN event
		*/
		private function onKeyDown(ev:KeyboardEvent): void
		{
			if (ev.keyCode == Keyboard.SPACE)
			{
				__useTryCatch = !__useTryCatch;
				__status.text = "Use try/catch block: " + __useTryCatch;
			}
		}
	}
}

To use this, simply focus the Flash app and press the space bar to toggle the try/catch block. The results I get are:

Environment Try/Catch No Try/Catch Slowdown
2.2 Ghz Intel Core 2 Duo, 2GB of RAM, Mac OS X 10.6 5.2 FPS 7.7 FPS 32%
3.0 Ghz Intel Core 2 Duo, 2GB of RAM, Windows XP 8.0 FPS 12.4 FPS 35%
Average 33.5%

A 33.5% slowdown is certainly not a 2-3x slowdown as I previously reported. This is not to say that 2-3x is not possible, since I have shown it to you already. However, it may be that 2-3x is the worst case scenario for a try/catch block slowdown. In this more realistic case you can see what is hopefully a more realistic slowdown figure. And still, 33.5% is certainly nothing to sneeze at. That, in a game, would be the difference between running at a smooth 30 FPS and a choppy 20 FPS. Are there other slowdown factors? Invariably! However, one thing seems certain: the slowdown is not, as suggested, negligible. I therefore repeat my recommendation to you: do not wrap performance-critical code in a try/catch block!