Loading images via http vs bytearray

Load images via http or using bytearray? which the best way?
It was long time since I wanted to perform this kind of test, but for different reasons I never had the time to do that.
Some days ago fnally I’ve found the time thanks to a project I’m involved into.
The problem.
A local webserver running flash remoting via pyamf and a flash standalone application which need to load tons of images locally.
I’ve noticed that sending from amf the list of images path to be loaded required too much time (for me), even if they’re loaded locally.
Because of this I decided to try a first benchmark loading a single image using the standard way: send the link from amf and then load it using the classic actionscript Loader.
The second test was to send directly from amf the image stream using a ByteArray and then loading the image in flash using the Loader.loadBytes method.
In this way I’ve noticed that the second task requires less time than the first one (more or less 40% less).
Unfortunately our application needs to load something like 50/100 images at the same time.
For this reason I did a new test loading 22 images of 500Kb each (10Mb total). For a better result I decided to use both pyamf and blazeds, to be sure there’s no problems in the language used. Moreover I used charles to register the benchmark of these tasks.
The results were unexpected!
First test: passing form amf the list of url images and loading using the standard flash Loader to load all of them simultaneously.
The time elapsed from the flash method call to the results was about 40 milliseconds. Then from the remote method result to the completion of all Loader about 1400 milliseconds.

Second test: passing from amf an arraycollection of bytearrays containing all the images stream to be loaded directly in flash.
The time elapsed form the flash method call to its result: about 2900 milliseconds. Time elapsed from the result to the all Loaders completion: about 900 milliseconds.
This results were unexpected for me, expecially because both with blazeds and pyamf sending 10Mb of bytearrays tooks something like 3 seconds!
At a first impression I thought the problem was the time for java and python to create the amf stream data, but after a deeper test I discovered that they took more or less 30ms to generate the amf stream and the real bottleneck was the http transfer of this stream data.

I’m attaching here the screenshots of the charles sessions using blazeds:

Here you can find also the complete benchmark result using flash trace within the swf:
————————–
BlazeDS
————————–
Method Elapsed-Time
call.test1 0
result.test1 40
complete.test1 1453
TEST1 TOTAL TIME: 1493
call.test2 0
result.test2 2919
complete.test2 921
TEST2 TOTAL TIME: 3840
————————–
PyAMF
————————–
call.test1 0
result.test1 32
complete.test1 953
TEST1 TOTAL TIME: 1049
call.test2 0
result.test2 2805
complete.test2 908
TEST2 TOTAL TIME: 4763
Here you can see the source code used for these tests:
In conclusion. While sending 10Mb of data ( for example bytearray ) requires more or less 2.5 seconds using flash remoting as single requests ( because the transfer rate of the webserver it’s about 4Mb/sec ), loading simultaneusly 20 images from the webserver, using http, tooks 1 second.
This is because the webserver for each requests opens a different thread to dispatch the request and in this way the total time to perform this task is less than the first method.

AMFPHP issue referencing ByteArray

Recently I made some test in AMFPHP sending an retrieving ByteArray to be stored in a mysql blob field.

But suddenly I encounter a problem when I was trying to return an array of ByteArrays and I could not find out the reason flash continued to show me this error:

The php file which was generating this error was:

class Service
{
public function getArray( )
{
$GLOBALS['amfphp']['encoding'] = 'amf3';
$a = new ByteArray("\x00\x10this is a string");
$b = new ByteArray("\x00\x16this is another string");
$c = $a;
return array( $a, $b, $c );
}
}

While the Actionscript test class was:

package
{
import flash.display.Sprite;
import flash.net.NetConnection;
import flash.net.ObjectEncoding;
import flash.net.Responder;
import flash.utils.ByteArray;

public class test extends Sprite
{
private var nc: NetConnection;
private var rsp: Responder;

public function test()
{
rsp = new Responder( onResult, null );
nc = new NetConnection();
nc.objectEncoding = ObjectEncoding.AMF3;
nc.connect( "http://localhost/amfphp/trunk/gateway.php" );
nc.call( "test.bytearray.Service.getArray", rsp );
}

private function onResult( e: * ): void
{
if ( e is Array )
{
for each ( var b: ByteArray in( e as Array ) )
{
trace( 'reading bytearray...' );
b.position = 0;
trace( b.readUTF() );
}
}
}
}
}


After some investigation I found that the error was into the AMFSerializer.php file in the writeAmf3ByteArray method which doesn’t reference correctly the bytearray objects.

Thus I made a little modification and after that I succeeded to retrieve correctly the bytearray list ( with the 3rd element as reference of the first one ).

Here you can download the patch file, if you want to test the modification ( at your own risk 🙂 ). The patch has been created using the last svn version of amfphp and you can apply the patch with the following command:
patch -p0 -i bytearray.patch

AMF news coming?

Today I’ve read a couple of guys who posted about the new Ted’s post on his blog.
Nothing new about amf, just some “best practices” on how to save using writeObject and readObject ByteArray’s methods.

But what caught my attention was the last sentence in his post:

“There is some big AMF news coming in December…. :)”

Damn, This is cool!
Does anyone know anything about this? I can’t wait!

AMFPHP 1.9 beta 2 – so fassssssst

On 26th January Patrick posted about the new amfphp beta which, thanks to the Emanuele Ruffaldi‘s php module (you can get the binary and source files here), seems to be “ridiculously faster“.

I had 5 minutes today for testing it. Just a simple test with an array of 1,000 objects and using ServiceCapture in background to see the performance differences between the older amfphp version and this one.
The new amfphp version gave me these results (I has to say I had many other programs running in background…):

Type: amf
This request bundled all of the following service calls:
amfphp.getList()
Request: 697 bytes
Response: 15968 bytes
Total: 16665 bytes
Response Time: 0.19 seconds

while the older one:

Type: amf
This request bundled all of the following service calls:
amfphp.getList()
Request: 697 bytes
Response: 16967 bytes
Total: 17664 bytes
Response Time: 0.541 seconds

So, incredible faster 🙂
This new beta comes with onther features such as gzip support, better recordset support and ByteArray support… but I strongly suggest to read the full article on Patrick’s blog here:
http://www.5etdemi.com/blog/…/amfphp-19-beta-2-ridiculously-faster/

ActionScript 3.0, first attempt

Finally I found the time (not so much) to play a little with Flex 2 alpha and ActionScript 3.0.
In particular I falled in love with the so long awaited ByteArray class, which let us to do bytes operations 🙂
So I’ve created a porting of the GNU gettext class for ActionScript. I’ve got this wish since a long long time, but it was always impossible without the new ByteArray class.
For the complete article read here, also for an explanation of gettext.

The project is hosted on google code here.

and this is the code I’ve used in flex:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.macromedia.com/2005/mxml" xmlns="*" creationComplete="main()">
<mx:Script>
<![CDATA[
import flash.events.*;
import flash.utils.trace;
import i18n.gettext;
import flash.net.*;
import mx.controls.*;
import mx.controls.gridclasses.DataGridColumn
var ln:gettext;
var alert:Alert;
var added:Boolean;
/**
* init gettext
*/
public function init_gettext()
{
ln = new gettext();
ln.addEventListener("complete", this.handleEvent);
ln.addEventListener("ioError",  this.handleEvent);
ln.addEventListener("error",    this.handleError);
set_locale("it");
}
public function set_locale(lang:String)
{
alert = Alert.show("Please wait while loading locale dictionary file", "Loading...", Alert.NONMODAL, this, null, null);
ln.translation("SEPY", "http://www.sephiroth.it/_temp/flex/gettext/locale/", lang);
ln.install();
log("innstall('" + lang + "')");
}
/**
* items for the combobox component
*/
public function get_avail_languages():Array
{
var data:Array = new Array();
data.push({label: _(gettext.FindLanguageInfo("it")), data:"it"});
data.push({label: _(gettext.FindLanguageInfo("en")), data:"en"});
data.push({label: _(gettext.FindLanguageInfo("de")), data:"de"});
data.push({label: _(gettext.FindLanguageInfo("zh_cn")), data:"zh_cn"});
data.push({label: _(gettext.FindLanguageInfo("zh_tw")), data:"zh_tw"});
data.push({label: _(gettext.FindLanguageInfo("nl")), data:"nl"});
data.push({label: _(gettext.FindLanguageInfo("fr")), data:"fr"});
data.push({label: _(gettext.FindLanguageInfo("pt")), data:"pt"});
return data;
}
/**
* items for the datagrid component
*/
public function get_dataprovider():Array
{
var data:Array = new Array();
data.push({label:"Add new folder", data:_("Add new folder")})
data.push({label:"An Error occurred, or the package need to be recompiled first", data:_("An Error occurred, or the package need to be recompiled first")})
data.push({label:"Are you sure?", data:_("Are you sure?")})
data.push({label:"Add @see if extended class (must be listed in the tags list)", data:_("Add @see if extended class (must be listed in the tags list)")})
data.push({label:"Browse new package", data:_("Browse new package")})
data.push({label:"Capture output", data:_("Capture output")})
data.push({label:"Cannot wrote to filesystem", data:_("Cannot wrote to filesystem")})
data.push({label:"Cannot read file, invalid zip file", data:_("Cannot read file, invalid zip file")})
data.push({label:"Cannot modify read-only document", data:_("Cannot modify read-only document")})
data.push({label:"Choose application", data:_("Choose application")})
return data;
}
/**
* main HandleEvent Responder
* handle all the event dispatched
*/
public function handleEvent(event:Event){
this.log("event: " + event.type);
switch(event.type)
{
case "click":
if(event.target.name == "button_1")
{
var mc:ComboBox = ComboBox(this.h_box_1.getChildByName("combo_languages"));
this.set_locale(mc.selectedItem.data);
} else {
this.callLater(init_gettext);
}
break;
case EventType.COMPLETE:
alert.visible = false;
if(!this.added)
{
var combo:ComboBox = new ComboBox();
var label:Label    = new Label();
var button:Button  = new Button();
var dp:DataGrid = new DataGrid();
var col_1:DataGridColumn = new DataGridColumn();
var col_2:DataGridColumn = new DataGridColumn();
var label_2:Label = new Label();
var hrule:HRule = new HRule()
button.name = "button_1"
button.addEventListener("click", this.handleEvent);
combo.name = "combo_languages";
label.name = "label_1";
this.h_box_1.addChild(label);
this.h_box_1.addChild(combo);
this.h_box_1.addChild(button);
dp.name = "datagrid_1";
dp.width = 560;
col_1.columnName = "label";
dp.addColumn(col_1);
col_2.columnName = "data";
col_2.headerText = _("Translation")
dp.addColumn(col_2);
label_2.name = "label_2";
hrule.width = 560;
this.v_box_1.addChild(label_2);
this.v_box_1.addChild(hrule)
this.v_box_1.addChild(dp);
this.added = true;
}
Button(this.h_box_1.getChildByName("button_1")).label = _("Change to");
Label(this.h_box_1.getChildByName("label_1")).text    = _("Language") + ": ";
Label(this.v_box_1.getChildByName("label_2")).text    = _("Test project");
ComboBox(this.h_box_1.getChildByName("combo_languages")).dataProvider = this.get_avail_languages();
DataGrid(this.v_box_1.getChildByName("datagrid_1")).dataProvider = this.get_dataprovider();
DataGrid(this.v_box_1.getChildByName("datagrid_1")).getColumnAt(0).headerText = _("Select items");
break;
default:
break;
}
}
/**
* Application init()
*/
public function main(){
var mc:Alert = Alert.show("Demo application using gettext for internationalization (i18n) puropose. Press the 'OK' button to load the English default language, then use the combo for switch between languages", "Flash and gettext", Alert.OK, this, null, null, Alert.OK);
mc.addEventListener("click", this.handleEvent);
}
/**
* shortcut usually used for
* gettext applications
*/
public function _(name:String):String
{
return gettext.translate(name);
}
public function log(text:String)
{
logger.text += text + "\n"
logger.vPosition = logger.maxVPosition
}
public function handleError(event:ErrorEvent)
{
log(event.text);
}
public function get_url()
{
navigateToURL(new URLRequest('http://www.gnu.org/software/gettext/'));
}
public function init_app():Void
{
Alert.show("Titolo", "testo", Alert.OK, this, null, null, Alert.OK);
}
]]>
</mx:Script>
<mx:Canvas width="100%" height="100%">
<mx:Label x="29" y="20" text="Gettext application demo" fontFamily="Georgia" fontWeight="bold" fontSize="18"/>
<mx:HRule x="33" y="39" width="560" height="20" themeColor="haloBlue"/>
<mx:HRule x="33" y="195" width="560" height="20" themeColor="haloBlue"/>
<mx:HRule x="33" y="89" width="560" height="20" themeColor="haloBlue"/>
<mx:Label x="32" y="111" text="log:" width="133"/>
<mx:Link x="369" y="26" label="http://www.gnu.org/software/gettext/" width="226" themeColor="haloBlue" textAlign="right" click="get_url()" toolTip="{_('Visit the GNU gettext project')}"/>
<mx:TextArea x="32" y="137" width="560" height="59" id="logger" editable="false" wordWrap="true"/>
<mx:HBox x="35" y="54" width="560" height="38" id="h_box_1" horizontalAlign="left" verticalAlign="middle" label="combo box languages">
</mx:HBox>
<mx:VBox id="v_box_1" horizontalAlign="left" verticalAlign="top" height="362" y="217">
<mx:layoutConstraints>
<mx:EdgeAnchor right="243" left="33"/>
</mx:layoutConstraints>
</mx:VBox>
</mx:Canvas>
</mx:Application>

http://code.google.com/p/sepy-gettext/