PDA

Volledige versie bekijken : Performance


FredericCox
%Europe/Berlin %406 %2008, 10:45
Een performance topic, dit wordt zo hard over het hoofd gezien heb ik de indruk, daarom zou ik deze topic willen gebruiken om te spreken over concrete bottlenecks en mogelijke oplossingen.

Zelf start ik met een kleine hang die ik aan het begin van mijn applicatie heb.

Dit omdat de functie vele for loops bevat die zoeken in objecten. Ik heb de functie opgesplitst in kleinere functies en de for loops getyped als Number

for(var i:Number = 0;i<data.length;i++)

Zijn er nog tips of zaken die ik kan proberen om die hang te voorkomen? Site mag iets langer laden zolang die maar niet blijft hangen.

Voor wie geinteresseerd is in de code (die herschreven moet worden vanaf nul binnenkort, ik weet het! :) )



public function createSearchProvider(module:String):void{
relevantFilterData = new Object();
//create the provider for filters and search box
if(availableFilters.length < 1){
availableFilters = new ArrayCollection([
{filterBox:this.CategorySearch, filterContainer:'categories', filterName:'category'},
{filterBox:this.BrandSearch, filterContainer:'brands', filterName:'brand'},
{filterBox:this.TopicSearch, filterContainer:'topics', filterName:'topic'},
{filterBox:this.StyleSearch, filterContainer:'styles', filterName:'style'}
]);
}

// var modules:Array = new Array("video","hopping","blog");
var moduleObject:Object = this[module+"Data"];
//set up the search data for each module (search in title/name, description/content, address, phoneNumber, brands, styles, topics, categories
moduleObject.moduleName = module;

moduleObject.brandProvider = getRelevantFilterData("brand",module);
moduleObject.categoryProvider = getRelevantFilterData("category",module);
moduleObject.styleProvider = getRelevantFilterData("style",module);
moduleObject.topicProvider = getRelevantFilterData("topic",module);
this.callLater(setupProvider,[module]);

}


private function setupProvider(module:String):void{
for(var a:Number = 0;a<availableFilters.length;a++){
var filter:Object = availableFilters[a];
determineSearchProvider(filter, module);
}
}

public function determineSearchProvider(filter:Object, module:String):void{
//To optimize search speed we already assign an array of id's to a filter where the item contains this filtervalue
var content:ArrayCollection;
var node:*;
switch(module){
case 'video':
content = this.parentDocument.videos;
break;
case 'hopping':
content = this.parentDocument.pois;
break;
case 'blog':
content = this.parentDocument.blogitems;
break;
}
for(var p:Number = 0;p<content.length;p++){
//for every item check if it matches the filters contents
var _check:Boolean = true;
switch(module){
case 'video':
try{
node = content[p][filter.filterContainer][filter.filterName];
}catch(err:Error){
var _check:Boolean = false;
}
break;
case 'hopping':
try{
node = content[p].poidetails[filter.filterContainer][filter.filterName];
}catch(err:Error){
var _check:Boolean = false;
}
break;
case 'blog':
try{
node = content[p][filter.filterContainer][filter.filterName];
}catch(err:Error){
var _check:Boolean = false;
}
break;
}


var filterValues:ArrayCollection = this[module+"Data"][filter.filterName+"Provider"];
for(var c:Number = 0; c<filterValues.length;c++){ //resultaat is bvb Kleding
if(_check){
//var _collection:* = node;
if(node is ArrayCollection){
for(var f:Number = 0;f<node.length;f++){
if(node[f].name.toLowerCase() == filterValues[c].name.toLowerCase()){
//this.parentDocument.debug.text += pois[p].poidetails.name + " behoort tot de " + filter.filterName + " " + hoppingData[filter.filterName+"Provider"][c].name + "\n";
//een filter(bvb category(kleding)) komt overeen met een filter uit de poi (kleding == kleding)
//dus moeten we kijken of deze winkel al eens is toegevoegd en anders dit id toevoegen aan de relevantKleding arrayCollection(id, module, matchType)
//als de relevant array nog niet bestaat maken we deze aan
if(this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()] == null){
this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()] = new ArrayCollection();
//add the new id to the arraycollection
var obj:Object = new Object;
obj.id = content[p].id;
obj.matchType = filter.filterName;
obj.module = module;
this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()].addItem(obj);
}else{
var found:Boolean = false;
for(var r:Number = 0;r<this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()].length;r++){
if(this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()][r] == content[p].id){
//Alert.show(pois[p].poidetails.name + "heeft meerdere ... ? en zit al in de array");
found = true;
}
}
if(!found){
//add the new id to the arraycollection
var obj:Object = new Object;
obj.id = content[p].id;
obj.matchType = filter.filterName;
obj.module = module;
this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()].addItem(obj);
}
}
}
}//end for
}else{

if(node.name.toLowerCase() == filterValues[c].name.toLowerCase()){
//this.parentDocument.debug.text += pois[p].poidetails.name + " behoort tot de " + filter.filterName + " " + hoppingData[filter.filterName+"Provider"][c].name + "\n";
//een filter(bvb category(kleding)) komt overeen met een filter uit de poi (kleding == kleding)
//dus moeten we kijken of deze winkel al eens is toegevoegd en anders dit id toevoegen aan de relevantKleding arrayCollection(id, module, matchType)
//als de relevant array nog niet bestaat maken we deze aan
if(this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()] == null){
this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()] = new ArrayCollection();
//add the new id to the arraycollection
var obj:Object = new Object;
obj.id = content[p].id;
obj.matchType = filter.filterName;
obj.module = module;
this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()].addItem(obj);
}else{
var found:Boolean = false;
for(var r:Number = 0;r<this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()].length;r++){
if(this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()][r] == content[p].id){
found = true;
}
}
if(!found){
//add the new id to the arraycollection
var obj:Object = new Object;
obj.id = content[p].id;
obj.matchType = filter.filterName;
obj.module = module;
this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()].addItem(obj);
}
}
}
}
}else{
//node is niet bruikbaar(ingevuld)
}
}
}
}
//this function determines which categories are in the dataProvider of the content and returns a dataProvider for the filterMenu
public var relevantFilterData:Object = new Object;
private function getRelevantFilterData(filter:String, moduleName:String = null):ArrayCollection{
if(moduleName == null){
moduleName = Application.application.moduleName;
}
//and set the one we return
var returnProvider:ArrayCollection = new ArrayCollection;
var content:ArrayCollection;
var isDetails:Boolean = false;

switch(moduleName){
case 'video':
content = this.parentDocument.videos;
break;

case 'hopping':
content = this.parentDocument.pois;
isDetails = true;
break;

case 'blog':
content = this.parentDocument.blogitems;
break;
}
content.filterFunction = null;
content.refresh();
returnProvider = returnCollectionForMenuFilter(content, filter, isDetails);
return returnProvider;
}
private function returnCollectionForMenuFilter(content:ArrayCollect ion, filter:String, isDetails:Boolean = false):ArrayCollection{
//determine which content we use
var returnProvider:ArrayCollection = new ArrayCollection;
var possibleFilters:ArrayCollection = new ArrayCollection([
{filterBox: this.CategorySearch, collectionName:'categories', objectName:'category'},
{filterBox: this.BrandSearch, collectionName:'brands', objectName:'brand'},
{filterBox: this.TopicSearch, collectionName:'topics', objectName:'topic'},
{filterBox: this.StyleSearch, collectionName:'styles', objectName:'style'}
]);
var collectionName:String;
var objectName:String;
switch(filter){
case 'category':
//search for the different categories in the content and return a dataProvider
collectionName = "categories";
objectName = "category";
break;

case 'brand':
collectionName = "brands";
objectName = "brand";
break;

case 'topic':
collectionName = "topics";
objectName = "topic";
break;

case 'style':
collectionName = "styles";
objectName = "style";
break;
}
//
var node:*;
var len:Number = content.length;
for(var i:Number = 0;i<len;i++){ //content.length
var found:Boolean = false;
try{
if(isDetails == true){
node = content[i]["poidetails"][collectionName][objectName];
}else{
node = content[i][collectionName][objectName];
}

if(node is ArrayCollection){
tryAddingCollection(node, returnProvider);
}else{
//check for duplicates else add them
tryAddingProxy(node, returnProvider);
}
}catch(error:Error){

}

}
var dataSortField:SortField = new SortField();
dataSortField.name = "name";
dataSortField.descending = false;
dataSortField.caseInsensitive = true;

/* Create the Sort object and add the SortField object created earlier to the array of fields to sort on. */
var DataSort:Sort = new Sort();
DataSort.fields = [dataSortField];

/* Set the ArrayCollection object's sort property to our custom sort, and refresh the ArrayCollection. */
returnProvider.sort = DataSort;
returnProvider.refresh();
return returnProvider;
}

private function tryAddingCollection(node:ArrayCollection, returnProvider:ArrayCollection):void{
for(var c:Number = 0;c<node.length;c++){
//check for duplicates else add them (if they belong to other filters)
/*var found:Boolean = false;
for(var d:Number = 0; d<returnProvider.length;d++){
if(returnProvider[d].id == node[c].id){
found = true;
}
}*/
//if(!found){



var _variable:String = makeCorrectVariable(node[c])
if(relevantFilterData[_variable] == null){
returnProvider.addItem(node[c]);
relevantFilterData[_variable] = 1;
}
//}
}
}
private function tryAddingProxy(node:Object, returnProvider:ArrayCollection):void{
/*var found:Boolean = false;
for(var d:Number = 0; d<returnProvider.length;d++){
if(returnProvider[d].id == node.id){
found = true;
}
}
if(!found){
*/
var _variable:String = makeCorrectVariable(node)
if(relevantFilterData[_variable] == null){
returnProvider.addItem(node);
relevantFilterData[_variable] = 1;
}
//}
}



Een hele brok ik weet het maar deze filters gelden voor 3 modules (blog, hopping en video). En dienen als rode draad door de navigatie.

Het is bovendien vrij oude code die ik in het begin van de site geschreven heb en besef dat dit niet echt mooie code is

TheDutch
%Europe/Berlin %453 %2008, 11:53
Probeer code zoveel als mogelijk op te delen in verschillende functie al heten ze dan misschien determineSearchProvider(), determineSearchProvider2(), determineSearchProvider3(). Een hang komt meestal voor wanneer één functie te veel moet uitvoeren waardoor het lang duurt voor het scherm geupdate wordt. Wanneer je dat opsplits in meerdere functie kan het scherm vaker geupdate worden en heb je dus minder hang :).

Je kunt dus delen van een lange functie opsplitsen in meerdere functies. Maar je kunt bijvoorbeeld ook bij een hele lange loop een recursieve functie maken waarbij je als parameters meegeeft van welke positie tot welke positie hij moet loopen. Op die manier verdeel je de loop load over meerdere functie aanroepen (al is het dezelfde functie).

Wanneer je vaak dezelfde regels gebruikt zoals bijvoorbeeld deze drie kan je die beter opslaan in een variable:
- filterValues[c].name.toLowerCase()
- this[module+"Data"]
- this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()]

Op die manier hoeft hij niet steeds de waarde "naam" op te halen uit dat object en bijvoorbeeld de functie toLowerCase() hoeft niet steeds uitgevoerd te worden. Scheelt ook een hoop performance.

FredericCox
%Europe/Berlin %501 %2008, 13:02
dus een functie met verschillende parameters wordt nog altijd als 1 functie behandeld?

FredericCox
%Europe/Berlin %519 %2008, 13:28
en is dit zo'n recursieve functie dan?



private var totalLength:Number = data.length; //bvb 850
private var maximumLoop:Number = 200;


private function doForLoop(i:Number):void{

var loopsRequired:Number = Math.round(totalLength / maximumLoop) ;
var thisLoopRequired:Number = totalLength / loopsRequired;

for(a:Number = i;a<((i*maximumLoop)+thisLoopRequired);a++){
//en loopen maar...
}

if(i != loopsRequired){
doForLoop(i++);
}

}

TheDutch
%Europe/Berlin %650 %2008, 16:36
dus een functie met verschillende parameters wordt nog altijd als 1 functie behandeld?
Nee.
en is dit zo'n recursieve functie dan?

Ja.

:)

FredericCox
%Europe/Berlin %743 %2008, 18:50
ok :)

theFlashWizard
%Europe/Berlin %992 %2008, 00:49
Ik dacht juist dat het script gerendert werdt na het uitvoeren van script. Als de ene funcie de andere uitvoert moet er dus nog script uitgevoert worden en snap ik niet waarom het scherm dan gerendert zou worden.
Ik zou zelfs zeggen dat alles in 1 functie stoppen qua preformance het beste is omdat functies aanroepen "duur" zijn.

Daarnaast zou ik voor for loops geen Number gebruiken omdat int sneller is. (Uit tests bleek zelfs dat int sneller was dan uint).
Ook bespaar je nog een klein beetje door de lenth van de for loop 1x uit te rekenen, op te slaan in een var en die te gebruiken.

TheDutch
%Europe/Berlin %337 %2008, 09:06
Daar heb je een punt TFW, was er vergeten bij te vertellen dat deze functies met callLater() (http://livedocs.adobe.com/flex/2/langref/mx/core/UIComponent.html#callLater()) of met een Timer (http://livedocs.adobe.com/flex/2/langref/flash/utils/Timer.html) uitgevoerd moeten worden om ervoor te zorgen dat ze pas bij de volgende frame update worden uitgevoerd. ActionScript binnen de Flash Player werkt helaas nog steeds single-threaded.

Wat misschien nog beter is om een event te dispatchen wanneer functie1() klaar is om functie2() te laten beginnen. Je zou zelfs een sequence event dispatcher voor kunnen maken die events queuet en achter elkaar uitvoert. De Flash Player werkt namelijk op de volgende manier vanaf het moment dat het op een frame komt: events >> scripts >> graphics.

ps. Functie aanroepen zijn inderdaad duur, maar we hebben het nu over een 'hang' voorkomen en niet over de uiteindelijke snelheid. De 'hang' is noodzakelijk om het script zo snel als mogelijk uit te voeren, maar echt wenselijk voor de eindgebruiker is het niet altijd. Zie ook de tekst bij het onderwerp helemaal bovenaan :).

theFlashWizard
%Europe/Berlin %528 %2008, 13:40
Ahh owke, ja ik dacht al.
Huh, maar events zijn eigenlijk ook niks anders dan functies aanroepen toch? Dit was dacht ik dus ook synchroon?
Op de events na die aangeroepen worden na het inladen van data bijv.
Ik ga het eens uitproberen.

Klopt, was niet helemaal van toepassing, maar waarschijnlijk evengoed interessant.

Heel irritant sinds ik player 10 heb geïnstalleerd wil FDT niet meer debuggen, ik hoop dat ze snel os x wat beter gaan ondersteunen.

theFlashWizard
%Europe/Berlin %617 %2008, 15:48
Eindelijk, na het opnieuw installeren van de debug versie van flash player 9 kon ik het weer via de browser testen. Ik blijf erbij, voor het geld dat je voor FDT betaald krijg je maar een onstabiel programma.

Owke, een kleine test gedaan. Ik heb een Main class en een Dispatcher class. Deze Dispatcher class dispatcht na een vertraging (timer) een event. De main class luistert hiernaar.
In de Dispatcher class heb ik voor en na de dispatchEvent een trace gezet. Ook heb ik in de event handler in de Main class een trace gezet.
Main:new Main
before event dispatching
TestEvent handler
after event dispatching

Gezien de volgorde kom ik tot de conclusie dat events in principe ook synchoon zijn en in dit geval niet gaan helpen.

Het idee van een FunctionQueue / FunctionChain utility lijkt me zeker een interessante oplossing.

TheDutch
%Europe/Berlin %666 %2008, 16:59
Heb zelf ook even een testje gedaan en inderdaad het dispatchen te samen met het afhandelen van events werkt synchroon, helaas!

Echter de events die gedispatched worden door een andere class zijn wel asynchroon met de acties in de huidige class. Een goed voorbeeld is de Timer class van de Flash Player zelf. Deze kunnen we gebruiken voor een generieke oplossing aangezien je kunt opgeven hoe vaak je wilt dat de Timer uitgevoerd wordt en elke tick een functie kan zijn. Proof of concept:

addEventListener(Event.ENTER_FRAME,onEnterFrame);

// Je kunt spelen met de msec a.d.h.v. de executie tijd van functies.
var timer:Timer = new Timer(1);
timer.addEventListener(TimerEvent.TIMER,onTimer);
timer.start();

function onTimer(event:TimerEvent):void
{
trace("Voer een functie uit!")
}

function onEnterFrame(event:Event):void
{
trace("--- Frame update ---");
}

Lijkt me een prima begin voor een zeer fijne oplossing :).

theFlashWizard
%Europe/Berlin %770 %2008, 19:30
Heb zelf ook even een testje gedaan en inderdaad het dispatchen te samen met het afhandelen van events werkt asynchroon, helaas!
Huh, Bedoel je niet juist synchroon hier?

Ik volg je verhaal misschien niet helemaal maar ik denk dat het in het geval van de timer class komt omdat die via een setInterval door het herhaaldelijk kijken naar getTimer "los komt" van het synchrone. Eigenlijk net zoals het COMPLETE event van de Loader class.
Dit zijn de dingen die met een "vertraging" werken en waarbij de events zich dus asynchroon opvolgen.
Als je in deze classes traces voor en na de dispatchEvent zou zetten zou je waarschijnlijk ook zien dat je de eventhandler eerst krijgt en dan pas de trace na de dispatchEvent.

TheDutch
%Europe/Berlin %776 %2008, 19:37
Huh, Bedoel je niet juist synchroon hier?

Klopt, ik pas het even aan :).

Ik volg je verhaal misschien niet helemaal maar ik denk dat het in het geval van de timer class komt omdat die via een setInterval door het herhaaldelijk kijken naar getTimer "los komt" van het synchrone. Eigenlijk net zoals het COMPLETE event van de Loader class.
Dit zijn de dingen die met een "vertraging" werken en waarbij de events zich dus asynchroon opvolgen.
Als je in deze classes traces voor en na de dispatchEvent zou zetten zou je waarschijnlijk ook zien dat je de eventhandler eerst krijgt en dan pas de trace na de dispatchEvent.
Je denkt volgensmij veel te ingewikkeld. Het dispatchen te samen met het afhandelen van een event werkt synchroon, omdat dit aan elkaar gerelateerd is. Echter een event kan in een bepaalde class gedispatched worden terwijl er code wordt uitgevoerd in een andere class die wellicht naar dat event luistert, geheel niet aan elkaar gerelateerd en dus asynchroon.

Naar mijn idee heeft elk stukje code wat op zichzelf wordt uitgevoerd een eigen thread binnen de Flash Player. Hierdoor kunnen twee classes asynchroon van elkaar code uitvoeren. Wanneer class1 code uitvoert en tegelijkertijd wordt er in class2 een event gedispatched waar class1 naar luistert dan zal dit tussen elkaar door gaan, asynchroon dus :).

FredericCox
%Europe/Berlin %718 %2008, 18:15
Welk effect zou het verhogen/verminderen van de frameRate hebben?

theFlashWizard
%Europe/Berlin %014 %2008, 01:21
het afhandelen van een event werkt synchroon\
Mee eens.

Nee volgens mij denk ik niet ingewikkeld. Ik denk niet dat er 2 stukken code tegelijkertijd uitgevoert kan worden, of ze nu in verschillende classes staan of niet. Omdat ActionScript zoals je zelf al zei single-threaded is.
Ik denk daarom ook dat het dispatchen van een event ongeveer hetzelfde effect heeft als het aanroepen van een functie. De handlers worden eerst uitgevoert en dan pas gaat hij weer verder in de dispatchende class.
In het geval van de timer event en bijv. het complete event van de loader class werkt het async omdat daar echt een vertraging in tijd in zit voordat die de event's dispatchen.


Verhogen framerate zou niks veranderen denk ik. Hij gaat toch wachten totdat het script uitgevoert wordt.

TheDutch
%Europe/Berlin %243 %2008, 06:50
Ik denk niet dat er 2 stukken code tegelijkertijd uitgevoert kan worden, of ze nu in verschillende classes staan of niet.
Ik heb mijn test nog even goed bekeken en er zat inderdaad een denk foutje in. Je hebt dus gelijk :).

Welk effect zou het verhogen/verminderen van de frameRate hebben?
Dit zou in jouw geval niets moeten uitmaken. De frame rate is namelijk alleen voor animaties of code in de onEnterFrame event handler.

TheDutch
%Europe/Berlin %254 %2008, 07:06
Heb je dit al gedaan:

Wanneer je vaak dezelfde regels gebruikt zoals bijvoorbeeld deze drie kan je die beter opslaan in een variable:
- filterValues[c].name.toLowerCase()
- this[module+"Data"]
- this[module+"Data"]["relevant_" + filterValues[c].name.toLowerCase()]

Dat zal denk ik een hele hoop schelen. Ook het opsplitsen van die ene functie in meerdere functies en dan met callLater() stuk voor stuk aanroepen zal de 'hang' kunnen voorkomen :).

FredericCox
%Europe/Berlin %341 %2008, 09:11
Ja dat eerste heb ik gedaan maar scheelt niet verbijsterend veel lijkt me, 2e gedeeltelijk maar nog niet met callLater() vannamiddag ga ik dat proberen

FredericCox
%Europe/Berlin %837 %2008, 21:06
Een recursieve for loop met callLater werkt verbazingwekkend! :) Vannamiddag had ik dit voor een opkomend project even nodig. Een echte aanrader :)

TheDutch
%Europe/Berlin %860 %2008, 21:38
Goed om te horen dat het zo goed werkt :).

theFlashWizard
%Europe/Berlin %035 %2008, 01:51
Heb je daar een handige utility voor kunnen schrijven? Zo ja ben ik wel zien :)

FredericCox
%Europe/Berlin %356 %2008, 09:33
Heb je daar een handige utility voor kunnen schrijven? Zo ja ben ik wel zien :)

Een utility nog niet, maar dit is mijn code:



private var totalHouses:Number = 1500000;
private var stepSize:Number = 50;
private function testPolygonPerformance(start:Number = 0):void{

var end:Number = 0;
if(start < (totalHouses-stepSize)){
end = start + stepSize;
}else{
end = start + (totalHouses - start);
}

for(var i:Number = start; i<end;i++){
ophalen.text = "ophalen gebouw " + (i+1);


createNewBuilding();
}

if(end != totalHouses){
this.callLater(testPolygonPerformance,[end]);
trace("nog bezig");
}else{
trace("end = " + end);
}
}

FredericCox
%Europe/Berlin %598 %2008, 15:21
Ik heb hier nog een bijkomende vraag:

Wat is de beste manier om een object waarvan je bepaalde waardes van nodig hebt en doorgeeft om dat te doen?


var item:Object = new Object;
item.name = currentPoi.poidetails.name;
item.id = currentPoi.id;
item.packageId = currentPoi.package.id;


en dan item meesturen naar de functie

of currentPoi meesturen? en dan in de andere functie die waardes opvragen, neemt het meesturen van currentPoi dan meer geheugen in of is het gewoon een verwijzing? currentPoi is eigenlijk een element in een arraycollectie

TheDutch
%Europe/Berlin %612 %2008, 15:42
Het is een verwijzing dus wanneer je er eerst een object van maakt kost dat alleen maar performance ipv. dat het performance scheelt. Echter kan het in veel gevallen handig zijn om met DTO's (Data Transfer Object), ook wel Value Objects genoemd, te werken welke je meegeeft aan functies, om een meer transparante interface te creëeren. Dit gaat (lichtelijk) ten koste van performance, maar komt ten goede aan de onderhoudbaarheid en leesbaarheid van de code :).

FredericCox
%Europe/Berlin %614 %2008, 15:44
Ik ga in dit geval dan toch voor de verwijzingen! Thanks :)

FredericCox
%Europe/Berlin %428 %2008, 11:16
Hier ben ik weer :)

Ik heb een weekje gepland om performance van mijn applicatie te verbeteren. Er staat namelijk vrij veel oude code nog in die ik nu in classes wil gaan gieten. Het voordeel van OOP is namelijk de overzichtelijkheid maar gaat daarmee ook meteen een betere performance bereikt worden?

Stel bijvoorbeeld dit:

Ik laad via AMFPHP een hele reeks objecten. Het is een vrij complexe structuur bijvoorbeeld de straat is poi.poidetails.street.name. Is het dan beter om bijvoorbeeld een datatype(=klasse die object extend?) POI te maken met daarin de functie getStreet?

En is het daarin nog beter om de waarde van getStreet op te slaan als een gewone var in de klasse POI voor als ik getStreet een 2de maal aanroep? (qua geheugen lijkt me dat niet?)

poiManager.getPoiById(5).getStreet()

bijvoorbeeld

TheDutch
%Europe/Berlin %616 %2008, 15:48
Zelf werk ik graag met Data Tranfer Objects (DTO) (http://en.wikipedia.org/wiki/Data_Transfer_Object). Deze zijn zeer snel te serializen/deserializen omdat het enkel de data bevat die je nodig hebt. Daarnaast gaat het hier om strictly typed objects waardoor het qua performance naar mijn weten sneller zou moeten zijn dan typeless objects.

PoiDTO

package
{
public class PoiDTO
{
public var name:String;
public var street:String;
public var zipcode:String;
public var city:String;
public var phone:String;
public var email:String;
// etc
}
}

Mocht je meerdere PoiDTOs terug kunnen krijgen, dan zou je een PoiCollection class kunnen maken waarin de PoiDTO instanties zich bevinden. Deze geef je dan met AMFPHP terug naar Flex/Flash.

PoiCollection

package
{
public class PoiCollection
{
private var _collection:Array;

public function PoiCollection()
{
this._collection = [];
}

public function addItem(item:PoiDTO):void
{
this.addItemAt(item, this._collection.length);
}

public function addItemAt(item:PoiDTO, index:int):void
{
this._collection.splice(index,0,item);
}

public function removeItem(item:PoiDTO):void
{
var loopItem:PoiDTO;

for(var i:int=0;i<this._collection.length;i++)
{
loopItem = this._collection[i] as PoiDTO;

if(loopItem === item)
{
this.removeItemAt(i);
}
}
}

public function removeItemAt(index:int):void
{
this._collection.splice(index,1);
}

public function removeAllItems():void
{
this._collection = [];
}

public function getItemAt(index:int):PoiDTO
{
return this._collection[index] as PoiDTO;
}

public function getItemIndex(item:PoiDTO):int
{
var loopItem:PoiDTO;

for(var i:int=0;i<this._collection.length;i++)
{
loopItem = this._collection[i] as PoiDTO;

if(loopItem === item)
{
return i;
}
}
}

public function get length():int
{
return this._collection.length;
}
}
}

Bovenstaande code is ter plekken neergezet en dus niet getest. Ik hoop dat je er wat aan hebt :).

FredericCox
%Europe/Berlin %622 %2008, 15:57
Super! Dus als ik het dan goed begrijp heb ik 1 functie die de poiData die via AMFPHP binnenkomt omzet naar DTO's wat dan het aantal lookups per object verminderd omdat die DTO slechts 1 variabele bevat per waarde die ik nodig heb. Dus krijg ik nooit meer streetName = poi.poidetails.address.street.name maar gewoon poiDTO.getItemAt(5).street :) zalig

Hoe zit dat met geheugen dan in Flash, mijn app gebruikt 101MB geheugen (20MB te wijten aan maptiles die geoptimaliseerd worden deze week) dus steeds nog 80MB kan dat aan die 1000 poi objecten liggen die ik binnenkrijg of heeft dat met eventlisteners en dergelijke te maken? Bij 100MB blijft het geheugen namelijk hetzelfde dus lijkt me geen lek :)

TheDutch
%Europe/Berlin %627 %2008, 16:03
Ik ging er vanuit dat je volledige class instanties van PHP naar Flex stuurde, klopt dit? In dat geval heb je dus aan beide kanten een PoiDTO en PoiCollection class. Je haalt de data op uit de database en zet deze met PHP in een PoiDTO object en die weer in een PoiCollection. Vervolgens stuur je dan dat PoiCollection object door naar Flex. In Flex zeg je dan poiCollection.getItemAt(5).street :).

Wanneer je XML binnenkrijgt in Flex dan kan je dat natuurlijk nog steeds omzetten naar een PoiCollection met PoiDTOs erin. Dat is altijd good practice om te doen binnen een goed gestructureerde applicatie.

Je kunt overigens ook DTOs nesten. Dus een PoiDTO met daarin een "address" property heeft van het type PoiAddressDTO welke weer properties heeft als "street" enzo. Lees je even in in DTOs zou ik zeggen.

Dan geheugengebruik kan best met de PoiDTOs te maken hebben, of eigenlijk gewoon met de data. Hoe meer blijvende data je hebt in je Flash Player sessie, hoe groter uiteraard het geheugengebruik.

FredericCox
%Europe/Berlin %631 %2008, 16:08
Op dit moment krijg ik van AMFPHP een array door met (ingewikkelde) objecten in :)

(Array)#0
[0] (Object)#1
id = "55"
poidetails = (Object)#2
address = (Object)#3
city = (Object)#4
id = "1"
name = "Antwerpen"
country = (Object)#5
id = "198"
name = "België"
mapaddress = (Object)#6
lat = (null)
lon = (null)
neighbourhood = (Object)#7
id = "1"
name = "Meir"
postcode = "2000"
province = (Object)#8
id = "1"
name = "Antwerpen"
region = (Object)#9
id = "1"
name = "Antwerpen centrum"
street = "Jezusstraat"
streetnumber = "10"
brands = (Array)#10
[0] "117"
[1] "118"
[2] "119"
[3] "120"
[4] "121"
[5] "122"
[6] "123"
[7] "124"
[8] "125"
[9] "126"
[10] "127"
[11] "128"
[12] "129"
[13] "130"
[14] "131"
building = (Array)#11

enz... :)

TheDutch
%Europe/Berlin %635 %2008, 16:14
Je kan dit dus omzetten naar PoiDTOs in een PoiCollection. Echter vraagt dat omzetten natuurlijk wel rekenkracht en dus kan het de performance beinvloeden. Na het omzetten kan de aanvraag wellicht wel sneller zijn dan wanneer je het niet omzet. Zelf zou ik het omzetten omdat het gewoon veel netter is en veel meer overzicht geeft. Qua performance zou je het eigenlijk zelf eens moeten testen. Het ligt er helemaal aan waar je graag de meeste performance ziet, bij het laden en verwerken of daarna. In het eerste geval zou het misschien beter zijn om het te laten hoe het nu is en bij het tweede geval is wellicht de DTOs+collection oplossing sneller. Probeer het dus even zelf uit. Zelf verkies ik overzicht en nette code boven performance wanneer de applicatie wel acceptabel performt bij de eindgebruiker :).

ps. Ik moet er vandoor, succes ermee!

FredericCox
%Europe/Berlin %646 %2008, 16:30
Laden en verwerken hebben we al goed gekregen, enige wat mij nog stoort is het geheugengebruik

Bedankt voor de info ik kan mij nu een weekje bezighouden! (ik vind deze topic een van de meest nuttige op het flex forum dus alle performance gerelateerde info van anderen is hier ook welkom)

TheDutch
%Europe/Berlin %654 %2008, 16:42
Dus het gaat niet meer om performance door CPU verbruik, duidelijk. Het geheugengebruik zal niet veel veranderen wanneer je over gaat op DTOs+collection, aangezien de hoeveelheid data gelijk blijft. De vraag is dus meer, waarom haal je 1000 records met data op? Het zou mij logischer lijken om enkel de data op te halen welke je op dat moment ook echt nodig hebt (lees: gebruikt).

Haal eens 10 records met data op ipv. 1000 en zie wat er gebeurd met het geheugengebruik. Dit om voor alle zekeheid toch nog even te checken of het door de hoeveelheid data komt, wat hoogstwaarschijnlijk is. Echter kan het ook aan fonts, graphics, etc liggen, dus bekijk dat even.

Nu ga ik er echt van tussen, anders ben ik zo direct te laat bij het vrouwtje :).

FredericCox
%Europe/Berlin %758 %2008, 19:12
Ik heb een meting gedaan voor en na ik de arraycollectie met 568 objecten vul. En dat scheelde in geheugen slechts enkele MB's. De 568 objecten zijn winkels, gebouwen die meteen op de kaart getoond moeten worden met de omtrek van hun gebouw om zo een mooi overzicht te krijgen. Kans is dus inderdaad groot dat het grote geheugenverbruik ligt bij de graphics (vele childs op de displayList enz). Ik ga het deze week eens onderzoeken.

Maar het grote structurele probleem (wat ongetwijfeld de performance beinvloed) is dat we met dit project vertrokken zijn met een mock up en door tijdsgebrek daarop verder zijn gaan bouwen :)

TheDutch
%Europe/Berlin %431 %2008, 10:20
Heb je ook al geprobeerd om echt maar 10 records terug te geven vanuit de server? Het vullen van een array collection kost natuurlijk geheugen, maar dan heb je reeds die 568 records ingeladen in de Flash Player sessie en weet je nog niet hoe het zou zijn met minder data, toch? Hoe dan ook, ik hoor je bevindingen wel deze week :).

Verder ontwikkelen op prototypes of mock-up's is inderdaad sterk af te raden. Heb ook vaak op die manier moeten ontwikkelen en meest van tijd koste het uiteindelijk toch meer tijd dan wanneer ik "from scratch" begonnen zou zijn. Erg vervelend!

TIP: Gebruik de profiler in Flex Builder 3 Professional om te bekijken waar het grote geheugengebruik wordt veroorzaakt.

FredericCox
%Europe/Berlin %724 %2008, 17:23
Je eerste zin vind ik interessant, ik laad de resultaten in via amfphp, dus voordat ik ze in de arraycollectie stop gebruiken ze al zoveel geheugen? En daarna als ik var p:Object = arraycoll.getItemAt(5) doe komt er geen geheugen bij omdat het een verwijzing is?

TheDutch
%Europe/Berlin %265 %2008, 06:21
Wanneer je de data in een array collection stopt komt er het dubbele aan data bij omdat het om primitieve waarden gaat en die dus gekopieerd worden (geen verwijzing dus). Alle data binnen de sessie van de Flash Player gebruikt geheugen, dus zal de data die ingeladen wordt met amfphp natuurlijk ook al geheugen gebruiken alvorens je het verwerkt tot een array collection. Om deze dubbele data te voorkomen zou je in elk geval na de verwerking tot array collection de "raw" data die je van amfphp terugkreeg moeten verwijderen zodat de GC zijn werk hierop kan doen en er weer geheugen vrij zal komen :).

FredericCox
%Europe/Berlin %536 %2008, 12:53
En aangezien in de resultHandler de arraycollectie gevuld wordt gaat de event.result.data door GC automatisch verwijderd worden na die functie?

TheDutch
%Europe/Berlin %705 %2008, 16:55
Dat is correct, wanneer je enkel en alleen een verwijzing naar de data hebt binnen de handler functie :).

ps. Heb je de Profiler binnen Flex Builder 3 Professional al eens gebruikt?
ps2. Heb je nu al eens maar 10 records teruggegeven vanuit PHP?

FredericCox
%Europe/Berlin %711 %2008, 17:03
In bijlage een eerste resultaat van de Flex profiler, 30% in gebruik voor canvassen of hoe zie ik dat? Ik gebruik wel heel veel canvassen... :s

FredericCox
%Europe/Berlin %712 %2008, 17:05
Dat is correct, wanneer je enkel en alleen een verwijzing naar de data hebt binnen de handler functie :).

ps. Heb je de Profiler binnen Flex Builder 3 Professional al eens gebruikt?
ps2. Heb je nu al eens maar 10 records teruggegeven vanuit PHP?

Voor profiler zie vorige post, ivm die 10 records... het is zo dat er voor de POI's ook al teveel geheugen gebruikt wordt naar mijn zin en dat probleem probeer ik eerst even te tackelen. Ik dacht eerst dat al de geheugenproblemen bij de POI's lagen maar blijkt niet zo te zijn, in de screenshot van de profiler in de vorige post heb ik trouwens heel het POI handling gedoe gecomment dus daar staat niets van in de profiler.

TheDutch
%Europe/Berlin %729 %2008, 17:30
Welke SDK gebruik je trouwens? 3.0 of 3.1?

FredericCox
%Europe/Berlin %733 %2008, 17:35
3.0.194161 , overigens merk ik dat mijn rode vierkentjes (redraw regions) ontzettend groot zijn en blijven, dit is ook niet echt normaal lijkt me (http://shopping.gva.be)

TheDutch
%Europe/Berlin %745 %2008, 17:54
De TabNavigator had een memory leak wat verholpen is in SDK 3.1, maar of dat jou zal helpen weet ik niet. De site is verder inderdaad erg traag door al dat geheugengebruik denk ik. Succes in elk geval met het analyseren van de boel, kan je vanaf hier niet direct zo helpen ben ik bang.

Misschien een idee om één voor één dingen uit te zetten? Die kaart met POIs lijkt me ook erg zwaar, maar dat kan schijn zijn :).

FredericCox
%Europe/Berlin %753 %2008, 18:04
Dat is geen schijn maar die had ik al uitgezet voor ik de profiler ging gebruiken. Ik gebruik wel vaak de tabnavigator (ook voor de grote delen video,hopping,blog en agenda). Ik ben aan het updaten naar 3.1.

Wel nog een performance vraagje, zoals je in het rapport van de profiler ziet gebruik ik een photoIcon in mijn flash skin. Is het normaal dat die zoveel geheugen gebruikt omdat elke nieuwe instantie (SWFLoader) dit icoontje laad? Is dit op te lossen door het icoontje 1x in te laden in een publieke SWFLoader en het zo te gebruiken of zijn hier betere manieren voor?