Hi Gaius,
Thanks for taking the time to help me get to the root of this problem and for evaluating the performance on the iOS platform. I did some further testing in an attempt to replicate your results. Here is where things get a bit interesting.
Initially, I could not see any improvement between the debug and release versions on the Android platform. However, when I exported the release with 'captive runtime' option (versus shared), the performance did improve. Upon digging further, I realized that it was not an issue of captive versus shared runtime, but rather a difference in the runtime versions. The captive runtime included in my build (and presumably yours) was version 3.1. The share runtime, however, was 3.4. If I set the target version to 3.4 using the latest SDK, and included the captive runtime, the performance was once again degraded. In summary here are my findings for the Android platform.
1) Debug (target 3.1, shared 3.4 runtime) : Slow.
2) Release (target 3.1, shared 3.4 runtime) : Slow.
3) Release (target 3.1, captive 3.1 runtime) : Fast.
4) Release (target 3.4, shared or captive 3.4 runtime) : Slow.
The difference between slow and fast are night and day (as you discovered) and seem to represent the difference between pooled and non pooled connections. I believe that connection pooling has somehow become broken between versions 3.1 and 3.4. Can you confirm these findings?
Thanks again,
Adam
Hmm. You were absolutely correct, that's a bit disappointing!
This is Android recompiled for 3.4 rather than 3.1.
null
COMPLETED 50 with 8 loaders in 11327 milliseconds or 226.54 per load. 50 8 11327 226.54
COMPLETED 50 with 50 loaders in 8899 milliseconds or 177.98 per load. 50 50 8899 177.98
COMPLETED 50 with 50 loaders in 9280 milliseconds or 185.6 per load. 50 50 9280 185.6
COMPLETED 50 with 50 loaders in 9513 milliseconds or 190.26 per load. 50 50 9513 190.26
COMPLETED 50 with 8 loaders in 9744 milliseconds or 194.88 per load. 50 8 9744 194.88
COMPLETED 50 with 1 loaders in 16383 milliseconds or 327.66 per load. 50 1 16383 327.66
Versus Apple iPad recompiled for 3.4 rather than 3.1.
null
COMPLETED 50 with 8 loaders in 502 milliseconds or 10.04 per load. 50 8 502 10.04
COMPLETED 50 with 50 loaders in 100 milliseconds or 2 per load. 50 50 100 2
COMPLETED 50 with 50 loaders in 117 milliseconds or 2.34 per load. 50 50 117 2.34
COMPLETED 50 with 50 loaders in 93 milliseconds or 1.86 per load. 50 50 93 1.86
COMPLETED 50 with 8 loaders in 270 milliseconds or 5.4 per load. 50 8 270 5.4
COMPLETED 50 with 8 loaders in 307 milliseconds or 6.14 per load. 50 8 307 6.14
COMPLETED 50 with 8 loaders in 316 milliseconds or 6.32 per load. 50 8 316 6.32
COMPLETED 50 with 4 loaders in 555 milliseconds or 11.1 per load. 50 4 555 11.1
COMPLETED 50 with 4 loaders in 547 milliseconds or 10.94 per load. 50 4 547 10.94
COMPLETED 50 with 4 loaders in 535 milliseconds or 10.7 per load. 50 4 535 10.7
COMPLETED 50 with 2 loaders in 1038 milliseconds or 20.76 per load. 50 2 1038 20.76
COMPLETED 50 with 2 loaders in 1042 milliseconds or 20.84 per load. 50 2 1042 20.84
COMPLETED 50 with 1 loaders in 2107 milliseconds or 42.14 per load. 50 1 2107 42.14
COMPLETED 50 with 1 loaders in 2099 milliseconds or 41.98 per load. 50 1 2099 41.98
Both are on release compile, which should take out all of the variability. So, yes, it looks as if the 3.4 AIR runtime has lost the connection pooling but ONLY for Android.
PS: Code I used in my test (which was a conventional View based Mobile app) is below in case you want to include it when you submit a bug report.
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="TestLLatency" xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function uicomponent1_creationCompleteHandler(event:FlexEvent):void
{
startTest();
}
public function startTest():void {
if(queue) throw new Error("Please wait for previous test to end.");
queue = new Array();
_count = 0;
var loader:Loader;
var i:int;
for (i = 0; i < numToQueue; i++) {
loader = bob.addChild(new Loader()) as Loader;
loader.x = (i%10)*50;
loader.y = Math.floor(i/5)*50;
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, complete);
queue.push(loader);
}
startTime = getTimer();
for(i=0;i<numLoaders;i++) {
nextQueue();
}
}
protected var startTime:int;
protected var endTime:int;
protected var numToQueue:int = 50;
protected var numLoaders:int = 8;
protected var queue:Array;
private var _count:int;
private function complete(event:Event):void
{
_count++
nextQueue();
}
[Bindable] protected var results:String;
protected function nextQueue():void {
var url:String = "http://fbcdn-profile-a.akamaihd.net/static-ak/rsrc.php/v2/yo/r/UlIqmHJn-SK.gif"; // 290 bytes
if(queue && queue.length) {
var loader:Loader = queue.pop() as Loader;
loader.load(new URLRequest(url));
} else if(_count==numToQueue) {
endTime = getTimer();
var elapsed:int = endTime-startTime;
results += "\n"+("COMPLETED "+numToQueue+" with "+numLoaders+" loaders in "+(elapsed)+" milliseconds or "+(elapsed/numToQueue)+" per load.\t"+[numToQueue,numLoaders,elapsed,elapsed/numToQueue].join("\t"));
queue = null;
while(bob.numChildren>5) {
bob.removeChildAt(bob.numChildren-1);
}
bob.getChildAt(0).addEventListener(MouseEvent.CLICK,repeatTest);
bob.getChildAt(1).addEventListener(MouseEvent.CLICK,repeatTest);
bob.getChildAt(2).addEventListener(MouseEvent.CLICK,repeatTest);
bob.getChildAt(3).addEventListener(MouseEvent.CLICK,repeatTest);
bob.getChildAt(4).addEventListener(MouseEvent.CLICK,repeatTest);
}
}
protected function repeatTest(event:MouseEvent):void {
var dob:DisplayObject = event.target as DisplayObject;
var testBehaviour:int = dob.parent.getChildIndex(dob);
try {
switch(testBehaviour) {
case 4 :
this.numLoaders = this.numToQueue;
break;
case 3 :
this.numLoaders = 8;
break;
case 2 :
this.numLoaders = 4;
break;
case 1 :
this.numLoaders = 2;
break;
case 0 :
this.numLoaders = 1;
break;
}
startTest();
dob.removeEventListener(MouseEvent.CLICK,repeatTest);
for(var i:int=0;i<5;i++) bob.removeChildAt(0);
} catch(e:Error) {
trace(e.message);
}
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:HGroup width="100%" height="100%">
<mx:UIComponent id="bob" width="100%" height="100%" creationComplete="uicomponent1_creationCompleteHandler(event)" />
<s:TextArea text="{results}" width="100%" height="100%" />
</s:HGroup>
</s:View>