Faster Functional Methods for Array and Vector
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; } } }
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 |
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!