Pages

Sunday, September 28, 2008

Flex Ruby performance with XML, JSON and RubyAMF

One of the great things when you use Flex in combination Ruby is that you can easily change the communication method. For instance you can use xml rpc or json rpc or RubyAMF, just by changing the ruby controller. Off course when you use json then you need to download the corelib for Flex and when you use RubyAMF we need to install this plugin in ruby.
But when you have a choice then you need to know what is the best communication method for your project. In this test I will measure the performance of the three methods. For this I made a simple Flex / Ruby project.

Here are the average results in ms (100 times executed) with the total records count in our test table.
records101004001000
amf159213380731
json4575252781
xml4357197612

Conclusion
RubyAMF has a little overhead and is fast with a large recordset and you can use remoteobject in Flex. Xml is fast but the performance can vary. With 1000 records it can take 200ms but sometimes 2000ms. It looks like Ruby can cache xml and sometimes refresh the cache. Json is very stable and fast with small recordsets. If you just want to retrieve some data in Flex, I would use json but when you want to do more with this data in Flex then RubyAMF has many benefits, like association between objects, conversion to actionscript class and a lot more.

The Ruby Code
you can test the output by adding the format parameter to the url http://localhost:3000/departments/find_all?format=json or format=xml.
Here is the controller code I used

class DepartmentsController < ApplicationController

# return all Departments
def find_all
respond_to do |format|
format.amf { render :amf => Department.find(:all) }
format.json { render :text => Department.find(:all).to_json }
format.xml { render :xml => Department.find(:all) }
end
end

end

department table created in a mysql database

class CreateDepartments < ActiveRecord::Migration

def self.up
create_table :departments do |t|
t.string :name
t.string :location
t.timestamps
end
end

def self.down
drop_table :departments
end
end

RubyAMF configuration

require 'app/configuration'
module RubyAMF
module Configuration
ClassMappings.ignore_fields = ['created_at','updated_at']
ClassMappings.translate_case = true
ClassMappings.assume_types = false
ParameterMappings.scaffolding = false

ClassMappings.register(
:actionscript => 'Department',
:ruby => 'Department',
:type => 'active_record',
:attributes => ["id", "name", "location", "created_at", "updated_at"])

ClassMappings.force_active_record_ids = true
ClassMappings.use_ruby_date_time = false
ClassMappings.use_array_collection = true
ClassMappings.check_for_associations = true
ParameterMappings.always_add_to_params = true
end
end


The Flex code

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import vo.Timing;

private var idNum:int;
private var maxNum:int = 100;

[Bindable]
private var timing:ArrayCollection = new ArrayCollection();

private function startRubyAMF():void {
timing = new ArrayCollection();
idNum = 0;
loadAll();
}
private function startJSON():void {
timing = new ArrayCollection();
idNum = 0;
loadAllJson();
}
private function startXML():void {
timing = new ArrayCollection();
idNum = 0;
loadAllXML();
}
]]>
</mx:Script>


<mx:Script>
<![CDATA[
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import mx.collections.ArrayCollection;

[Bindable]
private var departments:ArrayCollection = new ArrayCollection();
private var rubyDateFrom:Date;

private function loadAll():void {
rubyDateFrom = new Date();
var token:AsyncToken = AsyncToken(departmentService.find_all());
token.kind = idNum.toString();

var time:Timing = new Timing();
time.id = idNum;
time.startDate = rubyDateFrom;
timing.addItem(time);

}

private function faultHandler(event:FaultEvent):void {
Alert.show(event.fault.faultString + " : " + event.fault.faultCode + " : " + event.fault.faultDetail , "Error in LoginCommand");
}

private function resultHandler(event:ResultEvent):void {
departments = event.result as ArrayCollection;
var rubyDateFinish:Number = new Date().valueOf() - rubyDateFrom.valueOf() ;
rubyLabel.text="RubyAMF departments "+rubyDateFinish.toString()+" ms";

var time:Timing = timing.getItemAt(event.token.kind) as Timing;
time.endDate = new Date();
time.rubyamf = rubyDateFinish;

idNum = idNum +1;
if (idNum < maxNum ) {
loadAll();
} else {
var average:Number = 0;
for ( var i:int = 0 ; i < timing.length ; i++ ) {
var time2:Timing = timing.getItemAt(i) as Timing;
average = average + time2.rubyamf;
}
average = average / timing.length;
rubyLabel.text="RubyAMF departments average "+average.toString()+" ms";

trace("end");
}
}

]]>
</mx:Script>
<mx:HBox>
<mx:Button label="refresh RubyAMF" click="startRubyAMF()"/>
<mx:Button label="refresh JSON" click="startJSON()"/>
<mx:Button label="refresh XML" click="startXML()"/>
</mx:HBox>

<mx:RemoteObject id="departmentService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="DepartmentsController"
showBusyCursor="true"
result="resultHandler(event)"
fault="faultHandler(event)"
/>

<mx:Label id="rubyLabel" text="RubyAMF departments"/>

<mx:DataGrid id="dg" dataProvider="{departments}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

<mx:Script>
<![CDATA[
import com.adobe.serialization.json.JSON;
[Bindable]
private var dp:ArrayCollection = new ArrayCollection() ;
private var rubyJsonDateFrom:Date;

private function loadAllJson():void {
rubyJsonDateFrom = new Date();
var token:AsyncToken = AsyncToken(json.send());
token.kind = idNum.toString();

var time:Timing = new Timing();
time.id = idNum;
time.startDate = rubyJsonDateFrom;
timing.addItem(time);
}


private function resultHandlerJSON(event:ResultEvent):void
{
dp = new ArrayCollection();
var rawData:String = String(event.result);
var arr:Array = (JSON.decode(rawData) as Array);
for ( var i:int = 0 ; i < arr.length ; i++ ) {
dp.addItem(arr[i].department);
}
var rubyJsonDateFinish:Number = new Date().valueOf() - rubyJsonDateFrom.valueOf() ;
rubyJSONLabel.text="JSON departments "+rubyJsonDateFinish.toString()+" ms";

var time:Timing = timing.getItemAt(event.token.kind) as Timing;
time.endDate = new Date();
time.json = rubyJsonDateFinish;

idNum = idNum +1;
if (idNum < maxNum ) {
loadAllJson();
} else {
var average:Number = 0;
for ( var ii:int = 0 ; ii < timing.length ; ii++ ) {
var time2:Timing = timing.getItemAt(ii) as Timing;
average = average + time2.json;
}
average = average / timing.length;
rubyJSONLabel.text="JSON departments average "+average.toString()+" ms";

trace("end");
}


}
]]>
</mx:Script>

<mx:HTTPService id="json"
url="http://localhost:3000/departments/find_all?format=json"
result="resultHandlerJSON(event)" useProxy="false" />
<mx:Label id="rubyJSONLabel" text="JSON departments"/>
<mx:DataGrid id="dg4" dataProvider="{dp}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

<mx:Script>
<![CDATA[
import com.adobe.serialization.json.JSON;
[Bindable]
private var dp2:ArrayCollection = new ArrayCollection() ;
private var rubyXmlDateFrom:Date;

private function loadAllXML():void {
rubyXmlDateFrom = new Date();
var token:AsyncToken = AsyncToken(xml.send());
token.kind = idNum.toString();

var time:Timing = new Timing();
time.id = idNum;
time.startDate = rubyJsonDateFrom;
timing.addItem(time);


}

private function resultHandlerXML(event:ResultEvent):void
{
if ( event.result != null ) {
dp2 = event.result.departments.department;
}
var rubyXmlDateFinish:Number = new Date().valueOf() - rubyXmlDateFrom.valueOf() ;
rubyXmlLabel.text="XML departments "+rubyXmlDateFinish.toString()+" ms";

var time:Timing = timing.getItemAt(event.token.kind) as Timing;
time.endDate = new Date();
time.xml = rubyXmlDateFinish;

idNum = idNum +1;
if (idNum < maxNum ) {
loadAllXML();
} else {
var average:Number = 0;
for ( var ii:int = 0 ; ii < timing.length ; ii++ ) {
var time2:Timing = timing.getItemAt(ii) as Timing;
average = average + time2.xml;
}
average = average / timing.length;
rubyXmlLabel.text="XML departments average "+average.toString()+" ms";
trace("end");
}
}
]]>
</mx:Script>


<mx:HTTPService id="xml" url="http://localhost:3000/departments/find_all?format=xml"
result="resultHandlerXML(event)" useProxy="false" />
<mx:Label id="rubyXmlLabel" text="XML departments"/>
<mx:DataGrid id="dg5" dataProvider="{dp2}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

</mx:Application>

No comments:

Post a Comment