DomAPI Home
DomAPI
Build: 4.0
Topic: Memory growth when adding and removing components

  Paul Smith registered v4 wrote on Monday 3/27/06 at 4:52 PM (PST)  
 

We are planning on creating an application that dynamically (without refreshing the page) creates and removes many domapi objects (buttons, listgrids, elms, and probably other objects).

I made a test case that creates and removes a bunch of buttons to test what kind of memory performance I could expect. My test page has links to add and remove buttons 1 or 100 at a time.

Here are some numbers:

         Mem usage   VM size
Blank page      16068      7400
Load button test page   23380      11432
Add 100 buttons      27308      14844
Remove 100 buttons   28304      15836
Add 100 buttons      31340      18868
Remove 100 buttons   32296      19820
Add 100 buttons      35120      22784
Remove 100 buttons   36092      23756
Add 100 buttons      39044      26696
Remove 100 buttons   39956      27604
Add 100 buttons      42992      30636
Remove 100 buttons   43980      31636
Add 100 buttons      47072      34716
Remove 100 buttons   48020      35660
Add 100 buttons      51032      38680
Remove 100 buttons   51996      39640
Add 100 buttons      55040      42680
Remove 100 buttons   55964      43632
Add 100 buttons      58956      46596
Remove 100 buttons   59964      47596
Add 100 buttons      62948      50600
Remove 100 buttons   63876      51582
Add 100 buttons      66988      54644
Remove 100 buttons   67868      55532


Currently to remove buttons I call:
buttonToRemove.free();
buttonContainer.removeChild(buttonToRemove);
buttonToRemove.remove();

Am I doing anything wrong? Is there anything else I can do to reduce memory usage?

Here's my test page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd";><html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Button Example</title>
  <script type="text/javascript" src="src/domapi.js?styleBody=true&skin=true&theme=system"></script>
  <script type="text/javascript" src="tests/bin/examples.js"></script>
  <script type="text/javascript">

    var buttonNumber = 1;
    
    domapi.loadUnit("button");    
    onload = function(){
      exampleInit();
    }
    function checkBags()
    {
      var freedBags = 0;
      for(var i =0; i < domapi.bags.elms.length;i++)
      {
        if(domapi.bags.elms[i].freed == true) {freedBags++;}
      }
      alert("total bags = " + domapi.bags.elms.length + ". freed bags = " + freedBags);
    }
    
    function add100Buttons()
    {
      for(var i = 0; i < 100; i++)
      {
        add1Button();
      }
    }
    
    function delete100Buttons()
    {
      for(var i = 0; i < 100; i++)
      {
        delete1Button();
      }
    }
    
    function deleteAllButtons()
    {
      var buttonContainer = document.getElementById("buttonContainer");
      if(buttonContainer && buttonContainer.childNodes.length > 0)
      {
        while(buttonContainer.childNodes.length > 0)
        {
          var buttonToRemove = buttonContainer.childNodes[buttonContainer.childNodes.length - 1];
          buttonToRemove.free();
          buttonContainer.removeChild(buttonToRemove);
          buttonToRemove.remove();
        }
      }
    }
    
    function delete1Button()
    {
      var buttonContainer = document.getElementById("buttonContainer");
      if(buttonContainer && buttonContainer.childNodes.length > 0)
      {
        var buttonToRemove = buttonContainer.childNodes[buttonContainer.childNodes.length - 1];
        buttonContainer.removeChild(buttonToRemove);
        buttonToRemove.free();
        buttonToRemove.remove();
      }
    }
    
    function add1Button()
    {
      var buttonContainer = document.getElementById("buttonContainer");
      var button = domapi.Button({parent:buttonContainer,text: ("button" + buttonNumber)});
      buttonNumber++;
    }
  </script>
</head>
<body>
<script type="text/javascript">printHeader("Button")</script>
<A href="javascript:void(null);" onclick="add1Button(); return false;">Add 1 button</a>
<A href="javascript:void(null);" onclick="delete1Button(); return false;">Delete 1 button</a>
<A href="javascript:void(null);" onclick="add100Buttons(); return false;">Add 100 buttons</a> <A href="javascript:void(null);" onclick="delete100Buttons(); return false;">Delete 100 buttons</a>
<A href="javascript:void(null);" onclick="deleteAllButtons(); return false;">Delete all buttons</a>
<A href="javascript:void(null);" onclick="checkBags(); return false;">Check bags</a>






<div id=buttoncontainer></div> <script type="text/javascript">printFooter()</script> </body> </html>

 
    RSS feed of forum  
  Paul Smith registered v4 wrote on Monday 3/27/06 at 4:55 PM (PST)  
 

I should have mentioned that I'm using IE6.

 
    RSS feed of forum  
  Darin Kadrioski registered v4 wrote on Monday 3/27/06 at 5:21 PM (PST)  
 

We're going to have to come up with a better version of free(). Currently DomAPI doesn't do any IE memory mop-up until after the page unloads. It simply isn't geared to creating and dropping lots of components during a session right now. I'm currently mulling this over, and what the consequences would be. It'll be probably be an optional switch somewhere so as to not hamper performance during normal usage. (normal usage being defined as building a UI during onload, and keeping it around until the page unloads.)
Darin Kadrioski Darin Kadrioski
support@domapi.com

 
    RSS feed of forum  
  Simon G registered v4 wrote on Monday 3/27/06 at 5:34 PM (PST)  
 

I'd suggest using drip (http://www.outofhanwell.com/ieleak/) to track and then manually clean up any leaks.

Theres plenty of info around on how leaks are created (eg http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ietechcol/dnwebgen/ie_leak_patterns.asp) but trawling code looking for this stuff will make you batty. Drip on the other hand, shows you the actual leak, which makes finding the problem code and fixing it much easier.

 
    RSS feed of forum  
  Paul Smith registered v4 wrote on Monday 3/27/06 at 5:46 PM (PST)  
 

I'm a bit new to this - could you give me an example of one or two things in domapi that are preventing IE from releasing this memory, and why? I'm hoping to improve my ability to write memory-efficient javascript...

If you can give me a good idea of what to look for, and some suggestions on how to fix what I find, I'd be glad to make some changes and submit them to you (that is if you can fix my status from unregistered to registered - I do have a developer license on my account :).

Thanks for your help!

 
    RSS feed of forum  
  Henry registered v4 wrote on Monday 3/27/06 at 9:13 PM (PST)  
 

Before getting to your issue, you also need to take into consideration that you don't have control on how the browser do its garbage collection. Even if you free/remove objects carefully, the memory may not go down instantly. For FF1.5, it won't go down at all until you exit the process. (FF IS A MEMORY HOG!!!)

Now, back to your issue. I think what you did actually disrupt the domapi internal free/cleaning facility. look at domapi._unload see how much extra work is done there. somewhere down the road I think we can improve free/_unload with the function documented in http://www.crockford.com/javascript/memory/leak.html, for you testing, you will want to call the purge at the end of the remove.

Given what I said in the beginning, you may want to rethink why you need to constantly create and destroy component, try a different approach to your application.

Henry

 
    RSS feed of forum  
  Paul Smith registered v4 wrote on Monday 3/27/06 at 10:56 PM (PST)  
 

Thanks for your suggestions!

Simon, thanks for suggesting drip. I had heard of it, but never used it. If drip does not find any leaks, does that mean there's nothing else we can do to prevent the memory growth?

If I change these lines:

        buttonContainer.removeChild(buttonToRemove);
        buttonToRemove.free();
        buttonToRemove.remove();


to be these lines:
        buttonContainer.removeChild(buttonToRemove);
        domapi._freeComponent(buttonToRemove);
        buttonToRemove.remove();

then drip does not report any memory leaks. Perhaps the _free methods of each component should call _freeComponent/_freeElm to clean up any memory that they made when creating components/elms.



Henry, I'm very interested in the purge function. Do you think that if I did this for each elm/component that I am done with that there should be no memory leaks? If so, what's to stop domapi from doing this everytime we call .free()? I think that in my case it will be much faster to have the javascript correctly free up old components using purge than it would be to reload the entire page.

I understand that I have no control over when the garbage collector decides to run. As long as I have set everything up so that it can do its job I'll be happy for now. Later I'll investigate if there are some points in our application during which we could do a full page refresh without taking much of a performance hit. The reason we would like to reduce/eliminate full page rendering is that we can reduce network traffic, server load, and client load by responding with small XML messages. The client caches xml that describes the application's static or initial state and we just send field values and dynamic attributes that override the initial state.

I reviewed the _unload method and I don't see how freeing/removing stuff as I go would disrupt this mechanism. Am I missing something?

I should get a chance to try out the purge function tomorrow and post new numbers.

 
    RSS feed of forum  
  Darin Kadrioski registered v4 wrote on Monday 3/27/06 at 11:11 PM (PST)  
 

FYI, DomAPI was optimized against drip *long* ago. It shouldn't find any leaks outside of the weird ones we already know about (i.e Window).

Definitely let us know if you find any though.
Darin Kadrioski Darin Kadrioski
support@domapi.com

 
    RSS feed of forum  
  Henry registered v4 wrote on Monday 3/27/06 at 11:20 PM (PST)  
 

not too long, you just did the last fix for _iframeshield couple weeks ago *run* ...

Paul, what come to mind that you may cause a leaks is removing the element from the dom tree, so _unload or drip wont see it. look at what unload did, and read about why there is memory leaks issue in IE in the first place, then you will see the problem. purge will help for sure.

guess you should start to write a domapi.remove ;)

Henry

 
    RSS feed of forum  
  Paul Smith registered v4 wrote on Monday 3/27/06 at 11:26 PM (PST)  
 

Adding and removing buttons from my first example causes leaks in drip. Load the test page, add a button, remove a button, click [Check Leaks] and you'll get 2 objects. To fix it I had to call domapi._freeComponent. I think that since we should only need to call .free(), that it's a leak worth fixing.


Henry, I finally realized what you probably meant by disrupting _unload. None of the handlers get nulled out because free() doesn't do that...

 
    RSS feed of forum  
  Paul Smith registered v4 wrote on Tuesday 3/28/06 at 11:08 AM (PST)  
 

Well, my first attempt at using purge doesn't look good. Here are some new numbers:

                        Mem usage   VM size
Blank page      16284      7388
Load button test page   23560      11476
Add 100 buttons      27416      14852
Remove 100 buttons   35336      22752
Add 100 buttons      38232      25644
Remove 100 buttons   46072      33468
Add 100 buttons      48640      36184
Remove 100 buttons   56548      44080
Add 100 buttons      59436      46980
Remove 100 buttons   67164      54704


As you can see, freeing with purge causes IE to take up significantly more memory (at least until the page is unloaded).

After talking with Henry on IRC it seems like the best solution is to keep parts of the page (the ones in which we plan on adding and removing components) in iframes. When we remove the iframes not only can domapi perform it's cleanup, but IE can perform its own cleanup. No worrying about IE's garbage collector not firing when we want it to because it seems to happen when closing a page.

Thanks for your help everyone!

 
    RSS feed of forum  
  Paul Smith registered v4 wrote on Tuesday 3/28/06 at 11:11 AM (PST)  
 

If you're interested, this is the code change that I made to use purge:

    function purge(d) {
      var a = d.attributes, i, l, n;
      if (a) {
          l = a.length;
          for (i = 0; i < l; i += 1) {
              n = a[i].name;
              if (typeof d[n] === 'function') {
                  d[n] = null;
              }
          }
      }
      a = d.childNodes;
      if (a) {
          l = a.length;
          for (i = 0; i < l; i += 1) {
              purge(d.childNodes[i]);
          }
      }
    }
    function delete1Button()
    {
      var buttonContainer = document.getElementById("buttonContainer");
      if(buttonContainer && buttonContainer.childNodes.length > 0)
      {
        var buttonToRemove = buttonContainer.childNodes[buttonContainer.childNodes.length - 1];
        purge(buttonToRemove);
        buttonContainer.removeChild(buttonToRemove);
        buttonToRemove.free();
        buttonToRemove.remove();
      }
    }

 
    RSS feed of forum  
  Simon G registered v4 wrote on Wednesday 3/29/06 at 4:20 AM (PST)  
 

Sorry Paul, missed this question to me earlier:

"If drip does not find any leaks, does that mean there's nothing else we can do to prevent the memory growth?"

I think so. From what I understand, the main cause of leaks are when a DOM object references a JavaScript object, whereby the reference has been setup via a closure or some kind of circular link. Drip picks up these references for us, and shows the offending DOM elements in its leaks window.

But, as Henry pointed out, even with Drip not reporting any problems, IE & Firefox's garbage collection is rather arbitary, so memory use will grow until the browser decides to clean up.

BTW, I find Drip indespensable, for although the domApi is very clean with its object release, I find my own code is not quite to that standard :-) so I need to continually check. I use closures a lot - I just gotta be careful with their DOM references.

One tip with Drip - when you open the properties window on a DOM element, you can continually drill into each element property when the property is another object (& so open multiple property windows). Just means you can fully explore the DOM element to try and find where it is, what it links to.



 
    RSS feed of forum  
You could respond to this post if you were logged in.
DHTML by www.domapi.com