Proxy Performance
The Flash API has a gem of a class in Proxy. You can use it to customize the behavior of the dot (.), index ([]), delete, and in operators as well as the for-in and for-each-in loops. Today’s article answers a recent comment by exploring the performance implications of all this fancy customizing that Proxy allows.
To use Proxy, you must make a dynamic class that extends Proxy and overrides one or more of Proxy‘s methods to customize the behavior of the various operations listed above. Let’s start by looking at the WithProxy class that simply wraps a Dictionary using Proxy:
package { import flash.utils.Proxy; import flash.utils.flash_proxy; import flash.utils.Dictionary; public dynamic class WithProxy extends Proxy { private var __dict:Dictionary; private var __propNames:Vector.<String>; private var __numProps:int; public function WithProxy() { __dict = new Dictionary(); __propNames = new Vector.<String>(); } override flash_proxy function callProperty(methodName:*, ...args): * { var k:*; var dict:Dictionary = __dict; if (methodName == "countKeys") { var count:int; for (k in dict) { count++; } return count; } else if (methodName == "nullValues") { for (k in dict) { dict[k] = null; } } return undefined; } override flash_proxy function deleteProperty(name:*): Boolean { var ret:Boolean = (name in __dict); delete __dict[name]; return ret; } override flash_proxy function getDescendants(name:*): * { return __dict[name]; } override flash_proxy function getProperty(name:*): * { return __dict[name]; } override flash_proxy function hasProperty(name:*): Boolean { return name in __dict; } override flash_proxy function isAttribute(name:*): Boolean { return name in __dict; } override flash_proxy function nextName(index:int): String { return __propNames[index-1]; } override flash_proxy function nextNameIndex(index:int): int { if (index == 0) { var propNames:Vector.<String> = __propNames; propNames.length = 0; var size:int; for (var k:* in __dict) { propNames[size++] = k; } __numProps = size; } return (index < __numProps) ? (index+1) : 0; } override flash_proxy function nextValue(index:int): * { return __dict[__propNames[index-1]]; } override flash_proxy function setProperty(name:*, value:*): void { __dict[name] = value; } } }
And now a version that doesn’t use Proxy– the WithoutProxy class:
package { import flash.utils.Dictionary; public class WithoutProxy { private var __dict:Dictionary; public function WithoutProxy() { __dict = new Dictionary(); } public function countKeys(): int { var count:int; for (var k:* in __dict) { count++; } return count; } public function nullValues(): void { var dict:Dictionary = __dict; for (var k:* in dict) { dict[k] = null; } } public function deleteProperty(name:*): Boolean { var ret:Boolean = (name in __dict); delete __dict[name]; return ret; } public function getDescendants(name:*): * { return __dict[name]; } public function getProperty(name:*): * { return __dict[name]; } public function hasProperty(name:*): Boolean { return name in __dict; } public function isAttribute(name:*): Boolean { return name in __dict; } public function iterate(): Dictionary { var props:Dictionary = new Dictionary(); var dict:Dictionary = __dict; for (var k:* in dict) { props[k] = dict[k]; } return props; } public function setProperty(name:*, value:*): void { __dict[name] = value; } } }
To see the usage of these classes and to check their correctness, here’s a small app that does the same actions with both of them and displays the results side by side:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class ProxyTest extends Sprite { private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } public function ProxyTest() { __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); testWithProxy(); var loggerWidth:Number = __logger.width; __logger = new TextField(); __logger.x = loggerWidth + 5; __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); testWithoutProxy(); } private function testWithProxy(): void { log("----- WithProxy -----"); var proxy:WithProxy = new WithProxy(); log('proxy["Jackson"] = "Dunstan"'); proxy["Jackson"] = "Dunstan"; log('"Jackson" in proxy: ' + ("Jackson" in proxy)); log('proxy["Jackson"]: ' + proxy["Jackson"]); log("proxy.countKeys(): " + proxy.countKeys()); log("proxy.nullValues(): " + proxy.nullValues()); log('"Jackson" in proxy: ' + ("Jackson" in proxy)); log('proxy["Jackson"]: ' + proxy["Jackson"]); log('delete proxy["Jackson"]'); delete proxy["Jackson"]; log('"Jackson" in proxy: ' + ("Jackson" in proxy)); log('proxy["Jackson"] = "Programmer"'); proxy["Jackson"] = "Programmer"; log("proxy..Jackson: " + proxy..Jackson); log('proxy["Circle"] = "Round"'); proxy["Circle"] = "Round"; log("for-in..."); for (var k:* in proxy) { log(" " + k + " -> " + proxy[k]); } log("for-each-in..."); for each (var adjective:String in proxy) { log(" " + adjective); } } private function testWithoutProxy(): void { log("----- WithoutProxy -----"); var proxy:WithoutProxy = new WithoutProxy(); log('proxy.setProperty("Jackson", "Dunstan")'); proxy.setProperty("Jackson", "Dunstan"); log('proxy.hasProperty("Jackson"): ' + proxy.hasProperty("Jackson")); log('proxy.getProperty("Jackson"): ' + proxy.getProperty("Jackson")); log("proxy.countKeys(): " + proxy.countKeys()); log("proxy.nullValues(): " + proxy.nullValues()); log('proxy.hasProperty("Jackson"): ' + proxy.hasProperty("Jackson")); log('proxy.getProperty("Jackson"): ' + proxy.getProperty("Jackson")); log('proxy.deleteProperty("Jackson")'); proxy.deleteProperty("Jackson"); log('proxy.hasProperty("Jackson"): ' + proxy.hasProperty("Jackson")); log('proxy.setProperty("Jackson", "Programmer")'); proxy.setProperty("Jackson", "Programmer"); log('proxy.getDescendants("Jackson"): ' + proxy.getDescendants("Jackson")); log('proxy.setProperty("Circle", "Round")'); proxy.setProperty("Circle", "Round"); log("for-in..."); for (var k:* in proxy.iterate()) { log(" " + k + " -> " + proxy.getProperty(k)); } log("for-each-in..."); for each (var adjective:String in proxy.iterate()) { log(" " + adjective); } } } }
The results look like this:
----- WithProxy -----
proxy["Jackson"] = "Dunstan"
"Jackson" in proxy: true
proxy["Jackson"]: Dunstan
proxy.countKeys(): 1
proxy.nullValues(): undefined
"Jackson" in proxy: true
proxy["Jackson"]: null
delete proxy["Jackson"]
"Jackson" in proxy: false
proxy["Jackson"] = "Programmer"
proxy..Jackson: Programmer
proxy["Circle"] = "Round"
for-in...
Jackson -> Programmer
Circle -> Round
for-each-in...
Programmer
Round
----- WithoutProxy -----
proxy.setProperty("Jackson", "Dunstan")
proxy.hasProperty("Jackson"): true
proxy.getProperty("Jackson"): Dunstan
proxy.countKeys(): 1
proxy.nullValues(): undefined
proxy.hasProperty("Jackson"): true
proxy.getProperty("Jackson"): null
proxy.deleteProperty("Jackson")
proxy.hasProperty("Jackson"): false
proxy.setProperty("Jackson", "Programmer")
proxy.getDescendants("Jackson"): Programmer
proxy.setProperty("Circle", "Round")
for-in...
Circle -> Round
Jackson -> Programmer
for-each-in...
Round
ProgrammerFinally, let’s do a performance test to compare the classes:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class ProxyPerformance extends Sprite { private var __logger:TextField = new TextField(); private function row(...cols): void { __logger.appendText(cols.join(",") + "\n"); } public function ProxyPerformance() { __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); var beforeTime:int; var afterTime:int; var wTime:int; var woTime:int; var key:String; var val:String; var REPS:int = 100000; var SIZE:int = 100; var DELETE_KEY:String = "--keynotfound--"; var GET_KEY:String = "key0"; var SET_VAL:String = "val0"; var i:int; var wProxy:WithProxy = new WithProxy(); var woProxy:WithoutProxy = new WithoutProxy(); for (i = 0; i < SIZE; ++i) { key = "key" + i; val = "val" + i; wProxy[key] = val; woProxy.setProperty(key, val); } row("Function", "WithProxy", "WithoutProxy"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { wProxy.countKeys(); } afterTime = getTimer(); wTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { woProxy.countKeys(); } afterTime = getTimer(); woTime = afterTime - beforeTime; row("callProperty", wTime, woTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { delete wProxy[DELETE_KEY]; } afterTime = getTimer(); wTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { woProxy.deleteProperty(DELETE_KEY); } afterTime = getTimer(); woTime = afterTime - beforeTime; row("deleteProperty", wTime, woTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { wProxy..key0; } afterTime = getTimer(); wTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { woProxy.getDescendants(GET_KEY); } afterTime = getTimer(); woTime = afterTime - beforeTime; row("getDescendants", wTime, woTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { wProxy[GET_KEY]; } afterTime = getTimer(); wTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { woProxy.getProperty(GET_KEY); } afterTime = getTimer(); woTime = afterTime - beforeTime; row("getProperty", wTime, woTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { GET_KEY in wProxy; } afterTime = getTimer(); wTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { woProxy.hasProperty(GET_KEY); } afterTime = getTimer(); woTime = afterTime - beforeTime; row("hasProperty", wTime, woTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { wProxy[GET_KEY] = SET_VAL; } afterTime = getTimer(); wTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { woProxy.setProperty(GET_KEY, SET_VAL); } afterTime = getTimer(); woTime = afterTime - beforeTime; row("setProperty", wTime, woTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for each (key in wProxy) { val = key; } } afterTime = getTimer(); wTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for each (key in woProxy.iterate()) { val = key; } } afterTime = getTimer(); woTime = afterTime - beforeTime; row("for-each", wTime, woTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for each (val in wProxy) { key = val; } } afterTime = getTimer(); wTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for each (val in woProxy.iterate()) { key = val; } } afterTime = getTimer(); woTime = afterTime - beforeTime; row("for-each-in", wTime, woTime); } } }
I ran the performance test with the following environment:
- Flex SDK (MXMLC) 4.5.1.21328, compiling in release mode (no debugging or verbose stack traces)
- Release version of Flash Player 10.3.183.7
- 2.4 Ghz Intel Core i5
- Mac OS X 10.7.1
And got these results:
| Function | WithProxy | WithoutProxy |
|---|---|---|
| callProperty | 812 | 746 |
| deleteProperty | 88 | 29 |
| getDescendants | 32 | 10 |
| getProperty | 40 | 10 |
| hasProperty | 22 | 8 |
| setProperty | 43 | 12 |
| for-each | 3135 | 5367 |
| for-each-in | 3144 | 5383 |

Let’s cut out the loops, which make it hard to see the quicker functions:

As you can see, the performance of WithProxy is usually much worse than WithoutProxy. To summarize:
callPropertyis only marginally slower, probably due to theif-elsechain required- Everything else is about 3-4x slower (except loops)
- Exception:
for-inandfor-each-inperformance is about twice as fast. However, this may be due to my implementation and possible to overcome using an alternate technique. Certainly, creating a whole copy of theDictionary(as in both cases) is not optimal.
So, you should not be using Proxy in performance-critical code. In addition to forcing you into a slow dynamic class, it also mandates an API that often lacks types and shoehorns you into some slow patterns like an if-else chain for callProperty. The Proxy class does work though: it’s behavior is correct and it does allow you to override the default behavior of several AS3 operators. Just don’t use it where you need high performance.
There’s a lot of code above. Have you spotted a bug? Have a suggestion to optimize either version? Did I miss something about Proxy? By all means, post a comment about it!
#1 by J Murphy on February 27th, 2013 ·
This is exactly the information i was looking for. I’ve only worked with the Proxy class in other peoples code, and I’m working on defining a new Immutable Iterator Class that still allows for-each loops. This is a great starting point. Thanks for the article.