Now that AS3 is performing slower than JavaScript in some areas, should we be looking to optimize our AS3 by offloading tasks to JavaScript? That may sound perverse, but the possibility of major speedups is tempting. Today’s article looks for speedups using Flash’s AS3-to-JavaScript bridge: ExternalInterface.

Let’s address the elephants in the room first. For starters your SWF needs to be running in a browser so that there is a JavaScript environment to offload tasks to with ExternalInterface. There’s also a marshalling cost to transfer parameters to JavaScript function from AS3 and to transfer return values from JavaScript functions to AS3. This and the overhead of the calls themselves are quite expensive, so you really need to minimize the amount of data you transfer between the two languages and the number of JavaScript calls you make.

The following performance test is based on the first sub-test of the AS3 vs. JavaScript Performance series where an Array is joined into a String many times. As of last week, JavaScript took 3 milliseconds to do that task while AS3 took 100. So there’s a good reason to offload this task to JavaScript if possible. The test is modified to concatenate all of the joins into one very long String.

The JavaScript version of the test has been modified to take the Array, perform the join operations, and return the length of the joined String. There is a rather large marshalling cost to pass the Array to JavaScript but the return value marshalling cost is low since it is simply a number. Since the JavaScript function does all 500 joins in one function call rather than calling JavaScript 500 times, the overhead of calling the JavaScript function is reduced. Hopefully this is a decent simulation of a large offloaded task that would be typical for code hoping to use JavaScript as an optimization.

package
{
	import flash.text.TextField;
	import flash.external.ExternalInterface;
	import flash.utils.getTimer;
	import flash.display.Sprite;
 
	public class ExternalInterfaceOptimization extends Sprite
	{
		public function ExternalInterfaceOptimization()
		{
			var SIZE:int = 1000;	
			var REPS:int = 500;
			var beforeTime:int;
			var afterTime:int;
			var i:int;
 
			var tf:TextField = new TextField();
			tf.width = stage.stageWidth;
			tf.height = stage.stageHeight;
			tf.text = "Language,Time\n";
			addChild(tf);
 
			var array:Array = [];
			for (i = 0; i < SIZE; ++i)
			{
				array[i] = i + 'a';
			}
 
			beforeTime = getTimer();
			var asString:String = "";
			for (i = 0; i < REPS; ++i)
			{
				asString += array.join('-');
			}
			afterTime = getTimer();
			tf.appendText("AS3," + (afterTime-beforeTime) + "\n");
 
			beforeTime = getTimer();
			var jsStringLen:int = ExternalInterface.call("doJoin", array, REPS);
			afterTime = getTimer();
			tf.appendText("JavaScript," + (afterTime-beforeTime) + "\n");
 
			tf.appendText("\nSame string length? " + (asString.length==jsStringLen));
		}
	}
}

And here’s the JavaScript version of the function from the containing HTML page:

function doJoin(array, loops)
{
	   var str = "";
	   for (var i = 0; i < loops; ++i)
	   {
	      str += array.join('-');
	   }
	   return str.length;
}

Launch the test

I ran this test in the following environment:

  • Release version of Flash Player 11.7.700.169
  • Google Chrome 26.0.1410.65
  • 2.3 Ghz Intel Core i7
  • Mac OS X 10.8.3
  • ASC 2.0 build 352231 (-debug=false -verbose-stacktraces=false -inline)

And here are the results I got:

Language Time
AS3 112
JavaScript 11

Optimizing AS3 with JavaScript

These results mostly reflect what we saw last week with the 100-to-3 advantage of JavaScript over AS3. The test environment is a little different, but it does the original test in 108 milliseconds for AS3 and 4 milliseconds for JavaScript. Since the new version concatenates the join results we should expect it to take a little bit longer than the original. The JavaScript version should take a little bit longer too, but it now has all the ExternalInterface overhead: parameter marshalling, call overhead, return value marshalling.

That overhead is significant, but the performance advantage of JavaScript in this case is so large that JavaScript is still ~10x faster overall. This is probably not a case that you’ll want to include in your own apps, but there are many other similar scenarios where the same technique applies. Could your app use a 10x speedup in some areas?

Lastly, if ExternalInterface isn’t available or it happens to be slow you should probably have an AS3 version to fall back on. That’s as simple as an if statement:

// Abstract how the joins happen in a wrapper function
// Use [Inline] with ASC 2.0 for maximum performance
function doJoin(arr:Array, reps:int): String
{
    if (ExternalInterface.available)
    {
        return ExternalInterface.call("doJoin", arr, reps);
    }
    else
    {
        // AS3 version
        return result;
    }
}

Spot a bug? Have a question or suggestion? Post a comment!