Both Array and Vector have some methods that allow AS3 programmers to do some functional programming: every, filter, forEach, map, and some. These can lead to flexible and concise code, but at what performance cost? Today I’ll test them to get a handle on just how much speed you’re giving away by using these methods.

The functional methods are straightforward and well-documented by Adobe in both Vector and Array forms. To test their performance I set them up against two alternatives: a loop that calls the callback function and a loop that inlines the body of the callback function. You can see this over and over in the test code:

package
{
	import flash.display.*;
	import flash.events.*;
	import flash.text.*;
	import flash.utils.*;
 
	/**
	*   An app to test the functional programming methods of Array and Vector
	*   @author Jackson Dunstan
	*/
	public class FunctionalMethodsTest extends Sprite
	{
		private var logger:TextField = new TextField();
		private function log(msg:*): void { logger.appendText(msg + "\n"); }
 
		public function FunctionalMethodsTest()
		{
			logger.autoSize = TextFieldAutoSize.LEFT;
			logger.defaultTextFormat = new TextFormat("_sans", 6);
			addChild(logger);
 
			addEventListener(Event.ENTER_FRAME, testVector);
		}
 
		private function testVector(ev:Event): void
		{
			removeEventListener(Event.ENTER_FRAME, testVector);
 
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			var j:int;
			const SIZE:int = 7000;
			var v:Vector.<int> = new Vector.<int>(SIZE);
			var v2:Vector.<int>;
			var result:Boolean;
			const REPS:int = 7000;
 
			for (i = 0; i < SIZE; ++i)
			{
				v[i] = i;
			}
 
			log("Vector:");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.every(vectorReturnTrue);
			}
			afterTime = getTimer();
			log("\tevery method (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					if (!vectorReturnTrue(v[j], j, v))
					{
						break;
					}
				}
			}
			afterTime = getTimer();
			log("\tevery manual (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					// vectorReturnTrue always passes
				}
			}
			afterTime = getTimer();
			log("\tevery inline (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.every(vectorReturnFalse);
			}
			afterTime = getTimer();
			log("\tevery method (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					if (!vectorReturnFalse(v[j], j, v))
					{
						break;
					}
				}
			}
			afterTime = getTimer();
			log("\tevery manual (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					// vectorReturnFalse always fails
					break;
				}
			}
			afterTime = getTimer();
			log("\tevery inline (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.filter(vectorReturnTrue);
			}
			afterTime = getTimer();
			log("\tfilter method (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v2 = new Vector.<int>();
				for (j = 0; j < SIZE; ++j)
				{
					if (vectorReturnTrue(v[j], j, v))
					{
						v2.push(v[j]);
					}
				}
			}
			afterTime = getTimer();
			log("\tfilter manual (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v2 = new Vector.<int>();
				for (j = 0; j < SIZE; ++j)
				{
					// vectorReturnTrue always passes
					v2.push(v[j]);
				}
			}
			afterTime = getTimer();
			log("\tfilter inline (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.filter(vectorReturnFalse);
			}
			afterTime = getTimer();
			log("\tfilter method (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v2 = new Vector.<int>();
				for (j = 0; j < SIZE; ++j)
				{
					if (vectorReturnFalse(v[j], j, v))
					{
						v2.push(v[j]);
					}
				}
			}
			afterTime = getTimer();
			log("\tfilter manual (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v2 = new Vector.<int>();
				for (j = 0; j < SIZE; ++j)
				{
					// vectorReturnFalse always fails
					break;
				}
			}
			afterTime = getTimer();
			log("\tfilter inline (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.forEach(vectorReturnVoid);
			}
			afterTime = getTimer();
			log("\tforEach method: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					vectorReturnVoid(v[j], j, v);
				}
			}
			afterTime = getTimer();
			log("\tforEach manual: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					// vectorReturnVoid does nothing
				}
			}
			afterTime = getTimer();
			log("\tforEach inline: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.map(vectorReturnOne);
			}
			afterTime = getTimer();
			log("\tmap method: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v2 = new Vector.<int>(SIZE);
				for (j = 0; j < SIZE; ++j)
				{
					v2[j] = vectorReturnOne(v[j], j, v);
				}
			}
			afterTime = getTimer();
			log("\tmap manual: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v2 = new Vector.<int>(SIZE);
				for (j = 0; j < SIZE; ++j)
				{
					// vectorReturnOne always returns 1
					v2[j] = 1;
				}
			}
			afterTime = getTimer();
			log("\tmap inline: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.some(vectorReturnFalse);
			}
			afterTime = getTimer();
			log("\tsome method (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				result = false;
				for (j = 0; j < SIZE; ++j)
				{
					if (vectorReturnFalse(v[j], j, v))
					{
						result = true;
						break;
					}
				}
			}
			afterTime = getTimer();
			log("\tsome manual (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				result = false;
				for (j = 0; j < SIZE; ++j)
				{
					// vectorReturnFalse always fails
				}
			}
			afterTime = getTimer();
			log("\tsome inline (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v.some(vectorReturnTrue);
			}
			afterTime = getTimer();
			log("\tsome method (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				result = false;
				for (j = 0; j < SIZE; ++j)
				{
					if (vectorReturnTrue(v[j], j, v))
					{
						result = true;
						break;
					}
				}
			}
			afterTime = getTimer();
			log("\tsome manual (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				result = false;
				for (j = 0; j < SIZE; ++j)
				{
					// vectorReturnTrue always passes
					result = true;
					break;
				}
			}
			afterTime = getTimer();
			log("\tsome inline (none): " + (afterTime-beforeTime));
 
			addEventListener(Event.ENTER_FRAME, testArray);
		}
 
		private function testArray(ev:Event): void
		{
			removeEventListener(Event.ENTER_FRAME, testArray);
 
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			var j:int;
			const SIZE:int = 7000;
			var a:Array = new Array(SIZE);
			var a2:Array;
			var result:Boolean;
			const REPS:int = 7000;
 
			for (i = 0; i < SIZE; ++i)
			{
				a[i] = i;
			}
 
			log("Array:");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.every(arrayReturnTrue);
			}
			afterTime = getTimer();
			log("\tevery method (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					if (!arrayReturnTrue(a[j], j, a))
					{
						break;
					}
				}
			}
			afterTime = getTimer();
			log("\tevery manual (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					// arrayReturnTrue always passes
				}
			}
			afterTime = getTimer();
			log("\tevery inline (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.every(arrayReturnFalse);
			}
			afterTime = getTimer();
			log("\tevery method (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					if (!arrayReturnFalse(a[j], j, a))
					{
						break;
					}
				}
			}
			afterTime = getTimer();
			log("\tevery manual (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					// arrayReturnFalse always fails
					break;
				}
			}
			afterTime = getTimer();
			log("\tevery inline (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.filter(arrayReturnTrue);
			}
			afterTime = getTimer();
			log("\tfilter method (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a2 = new Array();
				for (j = 0; j < SIZE; ++j)
				{
					if (arrayReturnTrue(a[j], j, a))
					{
						a2.push(a[j]);
					}
				}
			}
			afterTime = getTimer();
			log("\tfilter manual (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a2 = new Array();
				for (j = 0; j < SIZE; ++j)
				{
					// arrayReturnTrue always passes
					a2.push(a[j]);
				}
			}
			afterTime = getTimer();
			log("\tfilter inline (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.filter(arrayReturnFalse);
			}
			afterTime = getTimer();
			log("\tfilter method (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a2 = new Array();
				for (j = 0; j < SIZE; ++j)
				{
					if (arrayReturnFalse(a[j], j, a))
					{
						a2.push(a[j]);
					}
				}
			}
			afterTime = getTimer();
			log("\tfilter manual (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a2 = new Array();
				for (j = 0; j < SIZE; ++j)
				{
					// arrayReturnFalse always fails
					break;
				}
			}
			afterTime = getTimer();
			log("\tfilter inline (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.forEach(arrayReturnVoid);
			}
			afterTime = getTimer();
			log("\tforEach method: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					arrayReturnVoid(a[j], j, a);
				}
			}
			afterTime = getTimer();
			log("\tforEach manual: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				for (j = 0; j < SIZE; ++j)
				{
					// arrayReturnVoid does nothing
				}
			}
			afterTime = getTimer();
			log("\tforEach inline: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.map(arrayReturnOne);
			}
			afterTime = getTimer();
			log("\tmap method: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a2 = new Array(SIZE);
				for (j = 0; j < SIZE; ++j)
				{
					a2[j] = arrayReturnOne(a[j], j, a);
				}
			}
			afterTime = getTimer();
			log("\tmap manual: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a2 = new Array(SIZE);
				for (j = 0; j < SIZE; ++j)
				{
					// arrayReturnOne always returns 1
					a2[j] = 1;
				}
			}
			afterTime = getTimer();
			log("\tmap inline: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.some(arrayReturnFalse);
			}
			afterTime = getTimer();
			log("\tsome method (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				result = false;
				for (j = 0; j < SIZE; ++j)
				{
					if (arrayReturnFalse(a[j], j, a))
					{
						result = true;
						break;
					}
				}
			}
			afterTime = getTimer();
			log("\tsome manual (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				result = false;
				for (j = 0; j < SIZE; ++j)
				{
					// arrayReturnFalse always fails
				}
			}
			afterTime = getTimer();
			log("\tsome inline (all): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				a.some(arrayReturnTrue);
			}
			afterTime = getTimer();
			log("\tsome method (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				result = false;
				for (j = 0; j < SIZE; ++j)
				{
					if (arrayReturnTrue(a[j], j, a))
					{
						result = true;
						break;
					}
				}
			}
			afterTime = getTimer();
			log("\tsome manual (none): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				result = false;
				for (j = 0; j < SIZE; ++j)
				{
					// arrayReturnTrue always passes
					result = true;
					break;
				}
			}
			afterTime = getTimer();
			log("\tsome inline (none): " + (afterTime-beforeTime));
		}
 
		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;
		}
	}
}

The results are voluminous so I’ll split them up by test:

Every (all pass)
Environment Method (Vector) Manual (Vector) Inline (Vector) Method (Array) Manual (Array) Inline (Array)
3.0 Ghz Intel Core 2 Duo, Windows XP 1516 500 (3.03x) 78 (19.43x) 1375 656 (2.09x) 63 (21.82x)
2.0 Ghz Intel Core 2 Duo, Mac OS X 3633 931 (3.9x) 122 (29.77x) 3545 1581 (2.24x) 123 (28.82x)
Every (none pass)
Environment Method (Vector) Manual (Vector) Inline (Vector) Method (Array) Manual (Array) Inline (Array)
3.0 Ghz Intel Core 2 Duo, Windows XP 0 0 0 0 0 0
2.0 Ghz Intel Core 2 Duo, Mac OS X 3 0 0 2 0 0
Filter (all pass)
Environment Method (Vector) Manual (Vector) Inline (Vector) Method (Array) Manual (Array) Inline (Array)
3.0 Ghz Intel Core 2 Duo, Windows XP 2219 1937 (1.15x) 1594 (1.39x) 2890 2594 (1.11x) 2109 (1.37x)
2.0 Ghz Intel Core 2 Duo, Mac OS X 5327 4263 (1.24x) 3376 (1.57x) 6034 5253 (1.14x) 3821 (1.57x)
Filter (none pass)
Environment Method (Vector) Manual (Vector) Inline (Vector) Method (Array) Manual (Array) Inline (Array)
3.0 Ghz Intel Core 2 Duo, Windows XP 1516 437 (3.46x) 0 1407 578 (2.43x) 0
2.0 Ghz Intel Core 2 Duo, Mac OS X 3555 835 (1.86x) 2 (1777x) 3245 1399 (2.31x) 5 (649x)
ForEach
Environment Method (Vector) Manual (Vector) Inline (Vector) Method (Array) Manual (Array) Inline (Array)
3.0 Ghz Intel Core 2 Duo, Windows XP 1500 500 (3x) 63 (23.8x) 1375 656 (2.09x) 79 (17.4x)
2.0 Ghz Intel Core 2 Duo, Mac OS X 3567 889 (4.01x) 98 (36.39x) 3310 1446 (2.28x) 98 (33.77x)
Map
Environment Method (Vector) Manual (Vector) Inline (Vector) Method (Array) Manual (Array) Inline (Array)
3.0 Ghz Intel Core 2 Duo, Windows XP 1953 781 (2.5x) 313 (6.23x) 2922 2282 (1.28x) 1390 (2.1x)
2.0 Ghz Intel Core 2 Duo, Mac OS X 4773 1468 (3.25x) 573 (8.32x) 6408 4207 (1.52x) 2534 (2.52x)
Some (all pass)
Environment Method (Vector) Manual (Vector) Inline (Vector) Method (Array) Manual (Array) Inline (Array)
3.0 Ghz Intel Core 2 Duo, Windows XP 1515 781 (1.93x) 313 (4.84x) 1375 656 (2.09x) 79 (17.4x)
2.0 Ghz Intel Core 2 Duo, Mac OS X 3618 935 (3.86x) 99 (36.54x) 3430 1448 (2.36x) 98 (35x)
Some (none pass)
Environment Method (Vector) Manual (Vector) Inline (Vector) Method (Array) Manual (Array) Inline (Array)
3.0 Ghz Intel Core 2 Duo, Windows XP 16 0 0 0 0 0
2.0 Ghz Intel Core 2 Duo, Mac OS X 2 0 0 2 0 0

There are a lot of stats up above, so let’s take it point by point:

  • Where appropriate, I’ve added a speed multiplier. This is not added when there are zeroes due to insufficient precision in the test.
  • The “manual” version always beats the “method” version. The speedup is in the range of 1.15x-4.01x. Getting double, triple, or quadruple the speed is a big improvement!
  • The “inline” version always beats the “manual” version and therefore the “method” version too. The speedup over the “method” version is in the range of 1.37x-1777x. Getting a 37% speedup is nice, but getting a 10x, 20x, or 1000x speedup is positively gigantic!
  • Statistically, there wasn’t much of a difference in speedup of either the “manual” or “inline” versions between Windows XP and Mac OS X.
  • Sometimes the functional methods run faster on Array (every, filter none, forEach, some all) and sometimes they run faster on Vector (filter all, map, some all).

In conclusion, there are humongous performance benefits to be had by not using the built-in functional methods of Array and Vector. Even if you want to use a function to offload the loop logic, you can still often double the loop speed and sometimes do much better. Far more effective than even that is to not offload the loop logic to a function, forego the many function calls, and do the loop the normal, non-functional way. It may speed up your loop by over 1000x!