05
Nov
09

Object pooling in Flex / ActionSctipt

Back in March of 2008 I published an object pooling utility class that allow you to place objects into a collection and use them when needed. That class came handy on a recent project where I had to use few instances of DataGrid, which is known to be one of the most expensive components in Flex which many believe should be avoided at all cost. The app I built had to create instances of data grid during runtime and than remove them from the display object. Object pooling is a great design pattern to handle these cases where you need to use the same object over again and again.

The reason is that object creation takes much resources and should be avoided in cases where you need the object often. I looked over the class and although I wrote it in 2008 it is still in good shape. One thing I noticed is that it was using an ArrayCollection. I have upgraded the class to use a Dictionary instead of ArrayCollection using a HashCollection utility class I developed which is using the Dictionary with a weak reference.

Download and look at the ReusablePool utility class:
http://code.google.com/p/eladlib/source/browse/trunk/EladLibFlex/src/com/elad/framework/objectpoolmanager/ReusablePool.as

To implement the class and show you an example I will be using an application that uses three objects: video, image and a list component. When you add the element to the application and then remove it and keep adding these objects agains and again you put a memory expense and you can see a small hickup. However, when you are using Object pool you keep the memory usage low since the objects are cached and you save on the object instantiation cost. As you can see the application is running smoothly while adding and removing objects from the display.

The complete implementation code can be seen below:


<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/halo"
			   minWidth="1024" minHeight="768"
			   creationComplete="setObjects()"
			   viewSourceURL="srcview/index.html">	

     <fx:Script>
          <![CDATA[
               import com.elad.framework.objectpoolmanager.Reusable;
               import com.elad.framework.objectpoolmanager.ReusablePool;

               import mx.collections.ArrayCollection;
               import mx.containers.Canvas;
               import mx.controls.Image;
               import mx.controls.VideoDisplay;
               import mx.core.UIComponent;

               import net.hires.debug.Stats;

               private var reusablePool:ReusablePool = ReusablePool.getInstance();
               private var canvas:Canvas = new Canvas;

               private function setObjects():void
               {
                    // adding stats
                    component.addChild( new Stats() );
                    addElement(component);                         

                    // adding the object to the collection
                    reusablePool.setReusable( new Reusable(createUI(), "FlexImage") );

                    // adding an array object
                    reusablePool.setReusable( new Reusable(createArray(), "arrayList") );

                    // adding video UI object
                    reusablePool.setReusable( new Reusable(createVideoUI(), "videoUI") );                                                            

                    this.addElement(canvas);
               }

               private function useUIObject(name:String):void
               {
                    // getting the collection
                    var reusable:Reusable = reusablePool.acquireReusable(name);
                    var component:UIComponent = reusable.object as UIComponent;

                    // use the object
                    canvas.removeAllChildren();
                    canvas.addElement(component);

                    // return collection back to the pool of objects
                    reusablePool.releaseReusable(reusable);
               }

               private function useArrayObject():void
               {
                    // getting the collection
                    var reusable:Reusable = reusablePool.acquireReusable("arrayList");
                    var dp:ArrayCollection = reusable.object as ArrayCollection;

                    list.dataProvider = dp;

                    // return collection back to the pool of objects
                    reusablePool.releaseReusable(reusable);
               }

               private function noCacheArrayObject():void
               {
                    var dp:ArrayCollection = createArray();
                    list.dataProvider = dp;
               }

               private function noCacheUIObject():void
               {
                    canvas.removeAllChildren();
                    canvas.addElement(createUI());
               }

               private function noCacheVideoUIObject():void
               {
                    canvas.removeAllChildren();
                    canvas.addElement(createVideoUI());
               }     

               private function createUI():UIComponent
               {
                    var image : Image = new Image();
                    image.source = "http://blog.digitalbackcountry.com/wp-content/uploads/flash_builder_logo.png";
                    image.width = 400;
                    image.height = 300;
                    image.x=10;
                    image.y=300;                    

                    return image;
               }

               private function createVideoUI():UIComponent
               {
                    var videoDisplay:VideoDisplay = new VideoDisplay();
                    videoDisplay.source = "http://kakonacl.dip.jp/PlayerTry/TestMovies/FLV/FLV4/Flash.flv";
                    videoDisplay.x = 10;
                    videoDisplay.y = 300;
                    videoDisplay.width = 300;
                    videoDisplay.height = 300;

                    return videoDisplay;
               }

               private function createArray():ArrayCollection
               {
                    var array:ArrayCollection = new ArrayCollection();

                    for (var i:int=0; i<10000; i++)
                    {
                         array.addItem({label: i});
                    }

                    return array;
               }               

          ]]>
     </fx:Script>

     <s:Label x="10" y="10"
                text="These examples shows memory usage with using the object pooling collection and without it."
                fontWeight="bold" height="33"/>

     <s:Label x="10" y="61"
                text="Cached Object Pooling" width="200" fontWeight="bold"/>

     <s:Button x="10" y="87"
                 label="UIobject"
                 click="useUIObject('FlexImage');" width="200"/>

     <s:Button x="10" y="117"
                 label="array"
                 click="useArrayObject();" width="200"/>                  

     <s:Button x="10" y="147"
                 label="Video UI"
                 click="useUIObject('videoUI');" width="200"/>

     <s:List id="list"
               x="325" y="79"
               width="126" height="200" />

     <s:Label x="10" y="178"
                text="Regular objects" width="200" fontWeight="bold"/>

     <s:Button x="10" y="202"
                 label="UIobject"
                 click="noCacheUIObject();" width="200"/>

     <s:Button x="10" y="232"
                 label="Array"
                 click="noCacheArrayObject()" width="200"/>          

     <s:Button x="10" y="262"
                 label="Video UI"
                 click="noCacheVideoUIObject()" width="200"/>

     <mx:UIComponent id="component" x="237" y="83" width="70" height="100"/>     

</s:Application>

screen-shot-2009-11-05-at-104133-pm

To measure the memory usage you can use the small statistic class that was developed by Mr.doob. Additionally, you can take a look at the Eclipse Profiler. In first scenario where I add an element to the application and remove it, the profiler shows as if we have a memory leak since the usage peaked to the “red” lines. I am not saying this is a memory leak, but I am pointing out that the memory usage is high since the objects were removed but haven’t been picked up by the GC yet. As you know using the removeAllChildren() remove the object from the component but is not necessarily ensure the GC will come and release the memory right away. See Profiler screen shot below:

screen-shot-2009-11-05-at-105952-pm

In the second scenario, the objects are cached and I am not putting any memory constrains on my application. See figure below.

screen-shot-2009-11-05-at-110137-pm

The utility class can be limited to the amount of objects you want to store, plus you can tie the class to a logic that will clear all the objects when needed.

Let’s take a look at the code to create an instance of the utility class and add an object.

The utility class is a singleton so we can use the same object through the life cycle of the application.


private var reusablePool:ReusablePool = ReusablePool.getInstance();

To add an object you create a Reusable object that holds the object and the name of the object:


// adding the object to the collection
reusablePool.setReusable( new Reusable(createUI(), "FlexImage") );

When you are ready to retrieve the object use the acquireReusable method, which will remove the object from the object collection:


// getting the collection
var reusable:Reusable = reusablePool.acquireReusable(name);
var component:UIComponent = reusable.object as UIComponent;

When you are done and want to send the object back to the object pool collection use:


reusablePool.releaseReusable(reusable);

Cheers :)


7 Responses to “Object pooling in Flex / ActionSctipt”


  1. 1 Jason Crist Nov 6th, 2009 at 1:27 am

    I’ve gotten HUGE memory and performance gains using this pattern. In some of my apps I render hundreds of components at a time, reuse is vital. Nice implementation of it. Certainly appreciate sharing the code.

    For those that don’t know the Flex list component family uses the same technique for the data renderers. I can’t say I’m familiar with Adobe’s implementation though. Expanding that idea to other areas is a big help.

  2. 2 David Wilhelm Nov 6th, 2009 at 7:56 am

    Thanks for sharing that.. may well use it at some point. Its not only the object creation that is expensive CPU-wise, but also the GC, so you get a double performance boost from re-using objects.

  3. 3 elad.ny Nov 6th, 2009 at 8:00 am

    Absolutely, thanks for mentioning the GC expense..

  4. 4 Ian McLean Nov 6th, 2009 at 12:02 pm

    Very cool. I’ve done something similar to this in the past which is to create a UIComponent singleton and place it in your application mxml. It can hold any components needed for re-use (creationPolicy an be set to all if needed). It works a lot like a Locator and you pull up a reference to an object using a static function. It has the benefit of being able define things in mxml and having a visual reference for whats in your object pool. It works pretty well for the quick and dirty.

  5. 5 elad.ny Nov 6th, 2009 at 11:51 pm

    Ian, can you share the code? BTW looking forward meeting you during FlashCamp Philly!

  6. 6 Jeannie Feb 15th, 2010 at 2:54 pm

    I would like to learn more about object pooling. However, the link seems to be broken.

    ReusablePool.as resulted in a 404 error. Can you make it available again?

  7. 7 elad.ny Feb 15th, 2010 at 7:59 pm

    Hi Jeannie, the source code moved to github: http://github.com/EladElrom/eladlib