ActionScript performance test for Array, Object, Vector literals and Array.push, Vector.push methods

Yesterday a “tweet” pointed me to a nice article about ActionScript 3.0 optimization. Most of these techniques are quite common within the ActionScript developer scene but one thing caught my attention: A link to Jackson Dunstans blog post about runtime performance and the const and final keywords. Since I always tried to use at least final classes I was a little disappointed that my extra work time doesn’t have any performance benefit at runtime.

Due to that circumstance I was curious about some other common techniques like “Use Object and Array Literals Whenever Possible” or “Add Elements to the End of an Array Without Pushing”. I wanted to check performance benefits for myself just to make sure it’s worth the effort. So I wrote a little test script:

var result : String = "Test playerType: " + Capabilities.playerType + " version: " + Capabilities.version + "\n",
	i : int = 0,
	c : int = 1000000,
	array : Array,
	object : Object,
	vectorInt : Vector.<int>,
	startTime : int;
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { object = new Object(); }
result += c + " times: object = new Object()             -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { object = {}; }
result += c + " times: object = {}                       -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { array = new Array(); }
result += c + " times: array = new Array()               -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { array = []; }
result += c + " times: array = []                        -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { vectorInt = new Vector.<int>(); }
result += c + " times: vectorInt = new Vector.<int>()    -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { vectorInt = new <int>[]; }
result += c + " times: vectorInt = new <int>[]           -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { array.push( i ); }
result += c + " times: array.push( i )                   -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { array[ array.length ] = i; }
result += c + " times: array[ array.length ] = i         -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { vectorInt.push( i ); }
result += c + " times: vectorInt.push( i )               -> duration: " + String( getTimer() - startTime ) + "\n";
 
startTime = getTimer();
for ( i = 0; i < c; i++ ) { vectorInt[ vectorInt.length ] = i; }
result += c + " times: vectorInt[ vectorInt.length ] = i -> duration: " + String( getTimer() - startTime ) + "\n";
 
var textFormat : TextFormat = new TextFormat();
	textFormat.font = "Courier";
	textFormat.size = 14;
 
var textField : TextField = new TextField();
	textField.autoSize = TextFieldAutoSize.LEFT;
	textField.background = true;
	textField.backgroundColor = 0xFFFFFF;
	textField.defaultTextFormat = textFormat;
	textField.multiline = true;
	textField.text = result;
 
addChild( textField );

It turns out that the benefits differ greatly on platform and player type but basically it’s always a good idea to avoid using the .push methods and new keyword. While there’s almost no speed difference in Flash Players and Plug-ins, the debug versions are really slow. And check out the blazing fast object creation with AIR on iOS! Or is this a compiler optimization? Here my results:


Flash CS 5 publish
Test playerType: External version: MAC 10,1,52,14

1000000 times: object = new Object() -> duration: 246
1000000 times: object = {} -> duration: 427
1000000 times: array = new Array() -> duration: 1288
1000000 times: array = [] -> duration: 339
1000000 times: vectorInt = new Vector.() -> duration: 586
1000000 times: vectorInt = new [] -> duration: 596
1000000 times: array.push( i ) -> duration: 134
1000000 times: array[ array.length ] = i -> duration: 137
1000000 times: vectorInt.push( i ) -> duration: 113
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 114


Flash Player Debugger.app
Test playerType: StandAlone version: MAC 11,1,102,62

1000000 times: object = new Object() -> duration: 179
1000000 times: object = {} -> duration: 311
1000000 times: array = new Array() -> duration: 1072
1000000 times: array = [] -> duration: 313
1000000 times: vectorInt = new Vector.() -> duration: 397
1000000 times: vectorInt = new [] -> duration: 402
1000000 times: array.push( i ) -> duration: 118
1000000 times: array[ array.length ] = i -> duration: 108
1000000 times: vectorInt.push( i ) -> duration: 111
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 89


Flash Player.app
Test playerType: StandAlone version: MAC 11,1,102,62

1000000 times: object = new Object() -> duration: 127
1000000 times: object = {} -> duration: 183
1000000 times: array = new Array() -> duration: 227
1000000 times: array = [] -> duration: 189
1000000 times: vectorInt = new Vector.() -> duration: 215
1000000 times: vectorInt = new [] -> duration: 218
1000000 times: array.push( i ) -> duration: 72
1000000 times: array[ array.length ] = i -> duration: 62
1000000 times: vectorInt.push( i ) -> duration: 61
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 42


Browser Debug Plug-in (Safari)
Test playerType: PlugIn version: MAC 11,1,102,62

1000000 times: object = new Object() -> duration: 157
1000000 times: object = {} -> duration: 249
1000000 times: array = new Array() -> duration: 1015
1000000 times: array = [] -> duration: 293
1000000 times: vectorInt = new Vector.() -> duration: 281
1000000 times: vectorInt = new [] -> duration: 282
1000000 times: array.push( i ) -> duration: 124
1000000 times: array[ array.length ] = i -> duration: 117
1000000 times: vectorInt.push( i ) -> duration: 124
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 94


Browser Plug-in (Google Chrome)
Test playerType: PlugIn version: MAC 11,2,202,235

1000000 times: object = new Object() -> duration: 147
1000000 times: object = {} -> duration: 146
1000000 times: array = new Array() -> duration: 237
1000000 times: array = [] -> duration: 224
1000000 times: vectorInt = new Vector.() -> duration: 257
1000000 times: vectorInt = new [] -> duration: 266
1000000 times: array.push( i ) -> duration: 54
1000000 times: array[ array.length ] = i -> duration: 61
1000000 times: vectorInt.push( i ) -> duration: 70
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 51


AIR SDK 3.2
Test playerType: Desktop version: MAC 11,2,202,223

1000000 times: object = new Object() -> duration: 274
1000000 times: object = {} -> duration: 243
1000000 times: array = new Array() -> duration: 1344
1000000 times: array = [] -> duration: 379
1000000 times: vectorInt = new Vector.() -> duration: 431
1000000 times: vectorInt = new [] -> duration: 426
1000000 times: array.push( i ) -> duration: 135
1000000 times: array[ array.length ] = i -> duration: 136
1000000 times: vectorInt.push( i ) -> duration: 153
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 74


iOS (iPad 3) debug interpreter
Test playerType: StandAlone version: MAC 11,1,102,62

1000000 times: object = new Object() -> duration: 4707
1000000 times: object = {} -> duration: 2033
1000000 times: array = new Array() -> duration: 22191
1000000 times: array = [] -> duration: 2993
1000000 times: vectorInt = new Vector.() -> duration: 10235
1000000 times: vectorInt = new [] -> duration: 10156
1000000 times: array.push( i ) -> duration: 1728
1000000 times: array[ array.length ] = i -> duration: 1670
1000000 times: vectorInt.push( i ) -> duration: 2056
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 1951


iOS (iPad 3) ad-hoc
Test playerType: Desktop version: IOS 11,2,202,223

1000000 times: object = new Object() -> duration: 1224
1000000 times: object = {} -> duration: 0 !!!
1000000 times: array = new Array() -> duration: 5886
1000000 times: array = [] -> duration: 1048
1000000 times: vectorInt = new Vector.() -> duration: 2737
1000000 times: vectorInt = new [] -> duration: 2784
1000000 times: array.push( i ) -> duration: 2043
1000000 times: array[ array.length ] = i -> duration: 234
1000000 times: vectorInt.push( i ) -> duration: 1524
1000000 times: vectorInt[ vectorInt.length ] = i -> duration: 643

Always worth reading: http://gskinner.com/talks/quick/

Safari Flash Player detection problems with ClickToFlash

I really like the concept of blocking Flash, mainly because I don’t see these unwanted, crappy developed Flash adds sucking performance, but also because it’s a nice way to recognise Flash parts in websites. You’ll have your own reasons.

Safari is, except for testing purposes, my browser of choice, but I was having trouble on different websites accessing Flash content because the Flash plugin was not detected correctly. Since I had to update the Flash Player on my system again, I decided to figure out what the problem is.

According to Playerversion.com an older Flash Player version – MAC 10,0,45,0 – was found first, then the display switched to my installed Flash version – MAC 10,3,181,14 (Debug player).

I used the Flash Player Uninstaller but Flash Player version MAC 10,0,45,0 was still displayed in Safari.

After some research I figured out that ClickToFlash doesn’t display the correct Flash Player version and therefore doesn’t work in all Flash detection scripts. Just check Safari - Help - Installed Plugins and search for "Flash" and you’ll see that ClickToFlash simulates somehow an outdated Flash Player version (seems to be hardcoded).

So until ClickToFlash will be updated displaying the correct installed Flash Player version I’ll use AdBlock For Safari. More Flash but less problems – hopefully. 🙂

Flash Performance Visualizer

Mr.doop’s Hi-ReS! Stats is a must have for every ActionScript developer. Seems to be still no. 1!
And I don’t want to search again… So here just the links:

Mr.doop’s blog | Hi-ReS! Stats
Mr.doop’s Stats.as @ GitHub

Update – Most recent version I found:
http://code.google.com/p/mrdoob/source/browse/trunk/libs/net/hires/debug/Stats.as

Undocumented XMLUtil class in Adobe Flash CS5?

I tried to recompile an older Flash CS 4 project with Flash CS 5 but got this compile time error 1061: Call to a possibly undefined method reuseNodes through a reference with static type Class caused by the usage of one of my utility classes:

This didn’t work

import de.superclass.util.XMLUtil;
...
XMLUtil.someMethod();

This still works

...de.superclass.util.XMLUtil.someMethod();

So there was no error in my class because it’s still usable when I use the full class package but it didn’t work when I just import it with the import statement.

After some research I found an undocumented XMLUtil class which avoids the correct access to my de.superclass.util.XMLUtil class.

This class doesn’t exist in Flash CS 4 but is available in Flash CS 5. Simply create a new FLA in Flash CS 5 an run this code on timeline

trace( describeType( XMLUtil ) );

and you’ll get this result:

<type name="XMLUtil" base="Class" isDynamic="true" isFinal="true" isStatic="true">
  <extendsClass type="Class"/>
  <extendsClass type="Object"/>
  <constant name="kAttrYMin" type="String"/>
  <constant name="kAttrXMax" type="String"/>
  <constant name="kAttrYMax" type="String"/>
  <constant name="kAttrObj0" type="String"/>
  <constant name="kAttrObj1" type="String"/>
  <constant name="kAttrX0" type="String"/>
  <constant name="kAttrY0" type="String"/>
  <constant name="kAttrX1" type="String"/>
  <variable name="kTagGravity" type="String"/>
  <constant name="kAttrY1" type="String"/>
  <variable name="kTagCollideSpace" type="String"/>
  <constant name="kAttrType" type="String"/>
  <variable name="kTagRigidBody" type="String"/>
  <constant name="kAttrPin" type="String"/>
  <variable name="kTagRect" type="String"/>
  <constant name="kAttrSlider" type="String"/>
  <variable name="kTagJoint" type="String"/>
  <constant name="kAttrLength" type="String"/>
  <variable name="kTagSpring" type="String"/>
  <constant name="kAttrStrength" type="String"/>
  <variable name="kTagFrames" type="String"/>
  <constant name="kAttrDamping" type="String"/>
  <variable name="kTagFrame" type="String"/>
  <constant name="kAttrSimSpeed" type="String"/>
  <variable name="kTagFluid" type="String"/>
  <constant name="kAttrAxis" type="String"/>
  <variable name="kTagEmitter" type="String"/>
  <constant name="kAttrAsset" type="String"/>
  <variable name="kTagOrigin" type="String"/>
  <constant name="kAttrSpring" type="String"/>
  <variable name="kTagKeyframes" type="String"/>
  <constant name="kAttrSpringStrength" type="String"/>
  <variable name="kTagKey" type="String"/>
  <constant name="kAttrSpringDamping" type="String"/>
  <variable name="kTagView" type="String"/>
  <variable name="kTagGeom" type="String"/>
  <constant name="kAttrIndex" type="String"/>
  <constant name="kAttrCollideType" type="String"/>
  <constant name="kAttrCellSize" type="String"/>
  <constant name="kAttrSpringValue" type="String"/>
  <constant name="kAttrCollideSpace" type="String"/>
  <constant name="kAttrRowDimen" type="String"/>
  <constant name="kAttrDensity" type="String"/>
  <constant name="kAttrColDimen" type="String"/>
  <constant name="kAttrPosX" type="String"/>
  <constant name="kAttrTimeStep" type="String"/>
  <constant name="kAttrPosY" type="String"/>
  <constant name="kAttrTime" type="String"/>
  <constant name="kAttrAngle" type="String"/>
  <constant name="kAttrColor" type="String"/>
  <constant name="kAttrVelX" type="String"/>
  <constant name="kAttrStrokeColor" type="String"/>
  <constant name="kAttrVelY" type="String"/>
  <constant name="kAttrAlpha" type="String"/>
  <constant name="kAttrVelA" type="String"/>
  <constant name="kAttrFPS" type="String"/>
  <constant name="kAttrCOR" type="String"/>
  <constant name="kAttrSprings" type="String"/>
  <constant name="kAttrCOF" type="String"/>
  <constant name="kAttrLimits" type="String"/>
  <constant name="kAttrFixed" type="String"/>
  <constant name="kAttrMinAngle" type="String"/>
  <constant name="kAttrID" type="String"/>
  <constant name="kAttrDBlend" type="String"/>
  <constant name="kAttrPoseStrength" type="String"/>
  <constant name="kAttrPoseDamping" type="String"/>
  <constant name="kAttrBodyDamping" type="String"/>
  <constant name="kAttrX" type="String"/>
  <constant name="kAttrY" type="String"/>
  <constant name="kAttrXMin" type="String"/>
  <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
  <method name="nodeHasAttribute" declaredBy="XMLUtil" returnType="Boolean">
    <parameter index="1" type="XML" optional="false"/>
    <parameter index="2" type="String" optional="false"/>
  </method>
  <method name="getIntAttribute" declaredBy="XMLUtil" returnType="int">
    <parameter index="1" type="XML" optional="false"/>
    <parameter index="2" type="String" optional="false"/>
    <parameter index="3" type="int" optional="true"/>
  </method>
  <method name="getNumberAttribute" declaredBy="XMLUtil" returnType="Number">
    <parameter index="1" type="XML" optional="false"/>
    <parameter index="2" type="String" optional="false"/>
    <parameter index="3" type="Number" optional="true"/>
  </method>
  <method name="getBoolAttribute" declaredBy="XMLUtil" returnType="Boolean">
    <parameter index="1" type="XML" optional="false"/>
    <parameter index="2" type="String" optional="false"/>
    <parameter index="3" type="Boolean" optional="true"/>
  </method>
  <method name="getStringAttribute" declaredBy="XMLUtil" returnType="String">
    <parameter index="1" type="XML" optional="false"/>
    <parameter index="2" type="String" optional="false"/>
    <parameter index="3" type="String" optional="true"/>
  </method>
  <factory type="XMLUtil">
    <extendsClass type="Object"/>
  </factory>
</type>

So I had to rename my class to de.superclass.util.XMLUtils.

You might run into the same problems when using mx.utils.XMLUtil (Flex Framework) or com.adobe.utils.XMLUtil (AS3CoreLib) but I didn’t test it!

UPDATE:
I found XMLUtil classes in
Adobe Flash CS5/Common/Configuration/ActionScript 3.0/libs/ik.swc and
Adobe Flash CS5/Common/Configuration/ActionScript 3.0/libs/PffLib.swc

Removing the $(AppConfig)/ActionScript 3.0/libs directory within the ActionScript export settings avoids the problem. But I have no time to test if this directory is necessary. If you run into problems please leave me a comment!

Aligning Flash TextField instances visually correct

I was looking for an easy way to align TextField instances with different font sizes visually correct. I really thought this wouldn’t take much time but it turned out to become really frustrating.

So think about a simple scenario: Try to align two TextField instances with different font sizes side by side. If you just set the TextField.y property to the same value you will recognise that different font sizes have different top margins. Well, you might fix that by hand if you have a static layout but what if you don’t know what kind of Text will be displayed? In my case I needed to align text always on top with the same margin regardless of the headline font size.

I tried hard to get this done by using different methods provided by the TextField class. Then I tried to work with the TextLineMetrics class as well. Nothing worked properly for me. I needed to work with CSS formatted HTML text so I was looking for a margin-top style or something similar. Not available. Hm.

Then I remembered my solution about finding the alpha bounds within external loaded PNG files and used that. Well, the main downside is that I need to create a BitmapData instance to determine the exact text bounds but otherwise it worked out very well.

It all comes down to two simple methods that will slip into my library. At the end you will receive a rectangle representing the bounds on the TextField instance.

If you know a different way to obtain the same exact result please leave me a comment!

Here’s my solution:

function getDisplayObjectAlphaBounds( displayObject : DisplayObject, smoothing : Boolean = true ) : Rectangle
{
	var bitmapData : BitmapData = new BitmapData( displayObject.width, displayObject.height, true, 0 );
		bitmapData.draw( displayObject, null, null, null, null, smoothing );
 
	var alphaBounds : Rectangle = bitmapData.getColorBoundsRect( 0x01000000, 0x01000000 );
 
	bitmapData.dispose();
 
	return alphaBounds;
}
 
function getTextFieldTextBounds( textField : TextField, smoothing : Boolean = true ) : Rectangle
{
	var background : Boolean = textField.background;
	var backgroundColor : uint = textField.backgroundColor;
	var border : Boolean = textField.border;
	var borderColor : uint = textField.borderColor;
	var opaqueBackground : Object = textField.opaqueBackground;
 
	textField.background = false;
	textField.border = false;
	opaqueBackground = null;
 
	var textBounds : Rectangle = getDisplayObjectAlphaBounds( textField, smoothing );
 
	textField.background = background;
	textField.backgroundColor = backgroundColor;
	textField.border = border;
	textField.borderColor = borderColor;
	textField.opaqueBackground = opaqueBackground;
 
	return textBounds;
}
 
var textFormat : TextFormat = new TextFormat();
	textFormat.align = TextFormatAlign.CENTER;
	textFormat.color = 0x0000FF;
	textFormat.font = "Arial";
	textFormat.size = 30;
 
var textField : TextField = new TextField();
	textField.border = true;
	textField.borderColor = 0x0000FF;
	textField.background = true;
	textField.backgroundColor = 0x000022;
	textField.defaultTextFormat = textFormat;
	textField.height = 260;
	textField.text = "\nThis text\ncan be\naligned\nproperly!";
	textField.width = 300;
	textField.x = 30;
	textField.y = 30;
 
addChild( textField );
 
// Get bounds
var textBounds : Rectangle = getTextFieldTextBounds( textField );
 
// Draw bounds
var shape : Shape = new Shape();
	shape.x = textField.x;
	shape.y = textField.y;
 
var g : Graphics = shape.graphics;
	g.lineStyle( 1, 0x00FFFF );
	g.drawRect( textBounds.x, textBounds.y, textBounds.width, textBounds.height );
 
addChild( shape );