Four years ago I tested the functional programming-style methods of Array and Vector: every, filter, forEach, map, and some. In that article I showed that these functions are much slower than doing the same task through traditional loops. Today’s article seeks to improve the performance of the functional methods while retaining readability by using ASC 2.0’s [Inline] metadata. Can homemade versions of these functions beat the built-in ones from Adobe? Read on to find out!

Each of these functions are very simple and mostly consist of a loop over the Array or Vector. Here’s the implementation of ArrayFunctional, a class full of the functional-style static helper functions:

package
{
	public class ArrayFunctional
	{
		[Inline]
		public static function every(
			a:Array,
			callback:Function
		): Boolean
		{
			for (var i:uint = 0, len:uint = a.length; i < len; ++i)
			{
				if (!callback(a[i], i, a))
				{
					return false;
				}
			}
			return true;
		}
 
		[Inline]
		public static function filter(
			a:Array,
			callback:Function
		): Array
		{
			var a2:Array = [];
			var len2:uint;
			for (var i:uint = 0, len:uint = a.length; i < len; ++i)
			{
				var cur:int = a[i];
				if (callback(cur, i, a))
				{
					a2[len2++] = cur;
				}
			}
			return a2;
		}
 
		[Inline]
		public static function forEach(
			a:Array,
			callback:Function
		): void
		{
			for (var i:uint = 0, len:uint = a.length; i < len; ++i)
			{
				callback(a[i], i, a);
			}
		}
 
		[Inline]
		public static function map(
			a:Array,
			callback:Function
		): Array
		{
			var len:uint = a.length;
			var a2:Array = new Array(len);
			for (var i:uint = 0; i < len; ++i)
			{
				a2[i] = callback(a[i], i, a);
			}
			return a2;
		}
 
		[Inline]
		public static function some(
			a:Array,
			callback:Function
		): Boolean
		{
			for (var i:uint = 0, len:uint = a.length; i < len; ++i)
			{
				if (callback(a[i], i, a))
				{
					return true;
				}
			}
			return false;
		}
	}
}

The Vector version is almost identical. For brevity, I’ve only added versions for the Vector.<int> class, but it should be trivial to add versions for the other three Vector classes. Here’s the class:

package
{
	public class VectorFunctional
	{
		[Inline]
		public static function everyInt(
			v:Vector.<int>,
			callback:Function
		): Boolean
		{
			for (var i:uint = 0, len:uint = v.length; i < len; ++i)
			{
				if (!callback(v[i], i, v))
				{
					return false;
				}
			}
			return true;
		}
 
		[Inline]
		public static function filterInt(
			v:Vector.<int>,
			callback:Function
		): Vector.<int>
		{
			var v2:Vector.<int> = new <int>[];
			for (var i:uint = 0, len:uint = v.length; i < len; ++i)
			{
				var cur:int = v[i];
				if (callback(cur, i, v))
				{
					v2.push(cur);
				}
			}
			return v2;
		}
 
		[Inline]
		public static function forEachInt(
			v:Vector.<int>,
			callback:Function
		): void
		{
			for (var i:uint = 0, len:uint = v.length; i < len; ++i)
			{
				callback(v[i], i, v);
			}
		}
 
		[Inline]
		public static function mapInt(
			v:Vector.<int>,
			callback:Function
		): Vector.<int>
		{
			var len:uint = v.length;
			var v2:Vector.<int> = new Vector.<int>(len);
			for (var i:uint = 0; i < len; ++i)
			{
				v2[i] = callback(v[i], i, v);
			}
			return v2;
		}
 
		[Inline]
		public static function someInt(
			v:Vector.<int>,
			callback:Function
		): Boolean
		{
			for (var i:uint = 0, len:uint = v.length; i < len; ++i)
			{
				if (callback(v[i], i, v))
				{
					return true;
				}
			}
			return false;
		}
	}
}

Now for the performance test. I’ve conducted the same test as the previous article except the “manual” and “inline” variants of each function have been replaced by the above [Inline] helper functions.

package
{
	import flash.display.*;
	import flash.text.*;
	import flash.utils.*;
 
	public class FunctionalMethodsTest extends Sprite
	{
		private var logger:TextField = new TextField();
		private function row(...cols): void
		{
			logger.appendText(cols.join(",") + "\n");
		}
 
		public function FunctionalMethodsTest()
		{
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			init();
		}
 
		private function init(): void
		{
			var beforeTime:int;
			var afterTime:int;
			var vMethodTime:int;
			var vHelperTime:int;
			var aMethodTime:int;
			var aHelperTime:int;
			var i:int;
			const SIZE:int = 5000;
			const REPS:int = 5000;
 
			var v:Vector.<int> = new Vector.<int>(SIZE);
			var a:Array = new Array(SIZE);
			for (i = 0; i < SIZE; ++i)
			{
				v[i] = i;
				a[i] = i;
			}
 
			row(
				"Function",
				"Vector",
				"VectorFunctional",
				"Array",
				"ArrayFunctional"
			);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.every(vectorReturnTrue);
			}
			afterTime = getTimer();
			vMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				VectorFunctional.everyInt(v, vectorReturnTrue);
			}
			afterTime = getTimer();
			vHelperTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.every(arrayReturnTrue);
			}
			afterTime = getTimer();
			aMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				ArrayFunctional.every(a, arrayReturnTrue);
			}
			afterTime = getTimer();
			aHelperTime = afterTime - beforeTime;
 
			row("every (all)", vMethodTime, vHelperTime, aMethodTime, aHelperTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.every(vectorReturnFalse);
			}
			afterTime = getTimer();
			vMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				VectorFunctional.everyInt(v, vectorReturnFalse);
			}
			afterTime = getTimer();
			vHelperTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.every(arrayReturnFalse);
			}
			afterTime = getTimer();
			aMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				ArrayFunctional.every(a, arrayReturnFalse);
			}
			afterTime = getTimer();
			aHelperTime = afterTime - beforeTime;
 
			row("every (none)", vMethodTime, vHelperTime, aMethodTime, aHelperTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.filter(vectorReturnTrue);
			}
			afterTime = getTimer();
			vMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				VectorFunctional.filterInt(v, vectorReturnTrue);
			}
			afterTime = getTimer();
			vHelperTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.filter(arrayReturnTrue);
			}
			afterTime = getTimer();
			aMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				ArrayFunctional.filter(a, arrayReturnTrue);
			}
			afterTime = getTimer();
			aHelperTime = afterTime - beforeTime;
 
			row("filter (all)", vMethodTime, vHelperTime, aMethodTime, aHelperTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.filter(vectorReturnFalse);
			}
			afterTime = getTimer();
			vMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				VectorFunctional.filterInt(v, vectorReturnFalse);
			}
			afterTime = getTimer();
			vHelperTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.filter(arrayReturnFalse);
			}
			afterTime = getTimer();
			aMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				ArrayFunctional.filter(a, arrayReturnFalse);
			}
			afterTime = getTimer();
			aHelperTime = afterTime - beforeTime;
 
			row("filter (none)", vMethodTime, vHelperTime, aMethodTime, aHelperTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.forEach(vectorReturnVoid);
			}
			afterTime = getTimer();
			vMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				VectorFunctional.forEachInt(v, vectorReturnVoid);
			}
			afterTime = getTimer();
			vHelperTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.forEach(arrayReturnVoid);
			}
			afterTime = getTimer();
			aMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				ArrayFunctional.forEach(a, arrayReturnVoid);
			}
			afterTime = getTimer();
			aHelperTime = afterTime - beforeTime;
 
			row("forEach", vMethodTime, vHelperTime, aMethodTime, aHelperTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.map(vectorReturnOne);
			}
			afterTime = getTimer();
			vMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				VectorFunctional.mapInt(v, vectorReturnOne);
			}
			afterTime = getTimer();
			vHelperTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.map(arrayReturnOne);
			}
			afterTime = getTimer();
			aMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				ArrayFunctional.map(a, arrayReturnOne);
			}
			afterTime = getTimer();
			aHelperTime = afterTime - beforeTime;
 
			row("map", vMethodTime, vHelperTime, aMethodTime, aHelperTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.some(vectorReturnFalse);
			}
			afterTime = getTimer();
			vMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				VectorFunctional.someInt(v, vectorReturnFalse);
			}
			afterTime = getTimer();
			vHelperTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.some(arrayReturnFalse);
			}
			afterTime = getTimer();
			aMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				ArrayFunctional.some(a, arrayReturnFalse);
			}
			afterTime = getTimer();
			aHelperTime = afterTime - beforeTime;
 
			row("some (all)", vMethodTime, vHelperTime, aMethodTime, aHelperTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.some(vectorReturnTrue);
			}
			afterTime = getTimer();
			vMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				VectorFunctional.someInt(v, vectorReturnTrue);
			}
			afterTime = getTimer();
			vHelperTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.some(arrayReturnTrue);
			}
			afterTime = getTimer();
			aMethodTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				ArrayFunctional.some(a, arrayReturnTrue);
			}
			afterTime = getTimer();
			aHelperTime = afterTime - beforeTime;
 
			row("some (none)", vMethodTime, vHelperTime, aMethodTime, aHelperTime);
		}
 
		private function vectorReturnTrue(val:int, index:int, vec:Vector.<int>): Boolean
		{
			return true;
		}
 
		private function vectorReturnFalse(val:int, index:int, vec:Vector.<int>): Boolean
		{
			return false;
		}
 
		private function vectorReturnVoid(val:int, index:int, vec:Vector.<int>): void
		{
		}
 
		private function vectorReturnOne(val:int, index:int, vec:Vector.<int>): int
		{
			return 1;
		}
 
		private function arrayReturnTrue(val:int, index:int, arr:Array): Boolean
		{
			return true;
		}
 
		private function arrayReturnFalse(val:int, index:int, arr:Array): Boolean
		{
			return false;
		}
 
		private function arrayReturnVoid(val:int, index:int, arr:Array): void
		{
		}
 
		private function arrayReturnOne(val:int, index:int, arr:Array): int
		{
			return 1;
		}
	}
}

Run the test

I tested this app using the following environment:

  • Release version of Flash Player 14.0.0.125
  • 2.3 Ghz Intel Core i7-3615QM
  • Mac OS X 10.9.2
  • Google Chrome 35.0.1916.153
  • ASC 2.0.0 build 354130 (-debug=false -verbose-stacktraces=false -inline -optimize=true)

And got these results:

Function Vector VectorFunctional Array ArrayFunctional
every (all) 659 796 575 753
every (none) 1 0 0 1
filter (all) 6000 7056 1219 1837
filter (none) 681 797 603 867
forEach 662 700 585 669
map 957 919 1274 1255
some (all) 648 787 576 745
some (none) 1 0 1 0

Functional Methods Test

Functional Methods Test (no filter)

Clearly, the [Inline] versions haven’t fared well. With the exception of map, they’re slower in every test for both Array and Vector. Sometimes it’s only a little slower, but sometimes there’s up to a 50% slowdown. These functions should therefore not be used except for the map version. With that, you can achieve a very small speed boost over the built-in Vector.map and Array.map functions.

The real speedup, as shown in the previous article, is when you can inline both the functional method (e.g. every()) and the callback function it “calls”. That function call overhead is huge, so inlining it will produce a gigantic speed boost compared to the built-in versions. Unfortunately, AS3 lacks a language feature to make this possible. In order to inline the callback you’ll need to abandon all functional programming style and write the loop yourself. So if you’re willing to sacrifice functional-style readability and conciseness, you do have a speedy option.

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